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 TextureFilter for adding textures to MeshVisuals #1444
Conversation
vispy/geometry/meshdata.py
Outdated
self._texcoords_indexed_by_faces is not None): | ||
self._compute_unindexed_texcoords() | ||
return self._texcoords | ||
elif indexed == 'faces': |
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.
This was copied over from get_vertices().
Should there be support for texcoords indexed by faces? (In which case _compute_unindexed_texcoords() can be implemented as in the case of vertices.)
@asnt I can't say too much to the interface for this right now. Maybe the other maintainers have stronger opinions. Do you have a self-contained example that I can test this with? Maybe you could add a new example script to the Thanks for all your work. |
There is machinery in VisPy to swap in GLSL code snippets for these sorts of purposes. Can you look to see how it's done with value-to-color mapping via a colormap? See e.g. this PR: |
Thank you all for your comments. @larsoner I'll have a look at implementing this using the function placeholders and pre/post hooks. I found some code [1] removed a while ago that seems related to the topic. Not sure yet if this is exactly what I want to do. @kmuehlbauer, is that what you were talking about?
@davidh-ssec I'll add an example. [1] 457657e#diff-fe7848e225c020030c1852544bc953bd |
@asnt Yes, this was what I was referring to. @campagnola, who did the scenegraph-implementation and also designed the So I would suggest to have a general discussion on implementing textured mesh via the filter-approach. Maybe if @campagnola can point us into the right direction and guidance we can get this implemented. @larsoner I didn't find anything about that texture-API which is referred to in the linked issue. Do you have any pointers? @asnt Does that make all sense to you? |
I linked to that PR not because of it being related to textures specifically, but rather about swapping in GL code as necessary using snippets and |
@larsoner Sorry, my bad. I meant not the issue that you linked but this one here (campagnola#10 (comment)). There @campagnola and @bollu were talking about texture API in context of |
It looks like the API hasn't really been hashed out there, and I don't recall it happening elsewhere. So I think it remains an open question. You could look at what |
@larsoner @kmuehlbauer The filter and the modular shader program approaches make sense. I'll have a try at some implementation. |
@asnt Awesome! |
@asnt I still lurk here now and then; let me know if anything needs explaining (and I will try to remember). There is one example that might help: |
The last commits define a TextureFilter to be used on the MeshVisual. Only the fragment shader is modified by the filter. The texcoords are currently part of MeshVisual by default because I couldn't find a way to adapt the main vertex/fragment programs from the filter to pass the texcoords attributes. I am not sure this is the desired interface. Any advice welcome. Note: Shading is skipped in MeshVisual at the moment for the sake of testing. An example was added |
vispy/visuals/mesh.py
Outdated
pass_texcoord['input'] = self._texcoords | ||
pass_texcoord['output'] = self.texcoord_varying | ||
vert_pre = self._get_hook('vert', 'pre') | ||
vert_pre.add(pass_texcoord()) |
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.
texcoords
are passed through the vertex shader only if texcoords are defined in the MeshVisual
Improving on the previous tentative implementation, 697db1f adds the texcoords to the shaders on demande. MeshVisual exposes a |
@asnt I did try the This works on the latest vispy master without your changes to The TextureFilter: from vispy.gloo import Texture2D, VertexBuffer
from vispy.visuals.shaders import Function, Varying
import numpy as np
class TextureFilter(object):
def __init__(self, texture, texcoords):
self.texture = texture
self.texcoords = texcoords
self._texcoords = VertexBuffer(np.zeros((0, 2), dtype=np.float32))
self._texcoord_varying = Varying('v_texcoord', 'vec2')
self.apply_coords = Function("""
void apply_coords() {
$v_texcoords = $texcoords;
}
""")
self.apply_texture = Function("""
void apply_texture() {
gl_FragColor *= texture2D($u_texture, $texcoord);
}
""")
self.coords_expr = self.apply_coords()
self.texture_expr = self.apply_texture()
def _attach(self, visual):
# vertex shader
vert_pre = visual._get_hook('vert', 'pre')
vert_pre.add(self.coords_expr)
tc = self.texcoords[visual.mesh_data.get_faces()]
self._texcoords.set_data(tc, convert=True)
self.apply_coords['texcoords'] = self._texcoords
self.apply_coords['v_texcoords'] = self._texcoord_varying
# fragment shader
frag_post = visual._get_hook('frag', 'post')
frag_post.add(self.texture_expr)
self.apply_texture['texcoord'] = self._texcoord_varying
self.apply_texture['u_texture'] = Texture2D(self.texture)
def _detach(self, visual):
visual._get_hook('vert', 'pre').remove(self.coords_expr)
visual._get_hook('frag', 'post').remove(self.texture_expr) The example program: import argparse
import numpy as np
from vispy import app, scene
from vispy.io import imread, read_mesh
from vispy.scene.visuals import Mesh
from vispy.visuals.filters import TextureFilter
parser = argparse.ArgumentParser()
parser.add_argument('--mesh', default='./data/spot.obj')
args = parser.parse_args()
vertices, faces, normals, texcoords = read_mesh(args.mesh)
texture = np.flipud(imread(args.mesh.replace(".obj", ".png")))
canvas = scene.SceneCanvas(keys='interactive', bgcolor='white', size=(800, 600))
view = canvas.central_widget.add_view()
view.camera = 'arcball'
shading = None
mesh = Mesh(vertices, faces,
shading=shading,
color='lightgreen')
tex = TextureFilter(texture, texcoords)
view.add(mesh)
canvas.show()
# attaching texture filter via timer
def on_timer1(event):
mesh.attach(tex)
timer2.start()
canvas.update()
# detaching texture filter via timer
def on_timer2(event):
mesh.detach(tex)
timer1.start()
canvas.update()
# one second oneshot-timer
timer1 = app.Timer(2., iterations=1, connect=on_timer1, start=True)
# one second oneshot-timer
timer2 = app.Timer(2., iterations=1, connect=on_timer2, start=False)
if __name__ == "__main__":
app.run() |
Thank you @kmuehlbauer Is the timers loop merely used for validating the attaching/detaching of the filter? |
@asnt This looks really great. Now I have some questions. Is this what you wanted to achieve? Would the I see the indexing problem too. Can you imagine a machinery to get this straight? Or does you current implementation work around this? I need to think about this. There is also my blunt use of @asnt As you are already very familiar now with Yes, the timer loop, was just for checking correct attach/detach mechanism. |
@kmuehlbauer Yes, this filter approach achieves what I wanted.
This would be used to temporarily hide the texture without detaching the filter. What do you think? Regarding the indexing, if I get it right, the problem seems to be that, on the first call to _update_data(), the vertices are not face-indexed, and on the subsequent calls, they are. So the indexing of the mesh changes after the filter has set up the non-face-indexed texcoords, and the texcoords buffer thus becomes incompatible in dimensions. I'll have a look at moving the shading code into their own filters. |
@asnt Yes, sure, the To the indexing, I really can't say why the indexing is done, without having a deeper look. But let's see what other opinions are out there. |
I haven't really worked with textures so I don't have any ideas here |
|
||
|
||
parser = argparse.ArgumentParser() | ||
parser.add_argument('--mesh', default='./data/spot.obj') |
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.
Should be a little more clever about finding the data; maybe:
data_path = os.path.join(os.path.dirname(__file__), 'data')
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.
@asnt I would suggest to put the data into http://github.com/vispy/demo-data/. We can retrieve it from there. Have a look how this is done in https://github.com/vispy/vispy/blob/master/examples/basics/scene/volume.py
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.
@kmuehlbauer Thank you. I'll do this change.
The indexing issue could be fixed by adding an event like |
@campagnola Thank you for the tips. In 33db5c8, MeshVisual._update_data() raises a What is reason behind having the indexing of vertices by faces? In the code there is this comment:
Moreover, from my experience implementing this filter, it seems that all meshes are drawn with face-indexed vertices after the first few draw events. (I'd have to double-check this, though.) |
Unfortunately I can't say much on that, so we would need @campagnola's expertise here. We should implement the easiest or fastest approach, sometimes this is the same. |
Travis tests are passing. Something is messed up with the integration so it isn't indicating that on the PR. @kmuehlbauer If you can look at the example script and try to figure out what it is supposed to do that'd be great. |
@djhoese I'll have a closer look the next day. |
I fixed the input to imap (the translation component had to be 0 because only interested in the rotation of the camera, not the global shift). For the record, the idea is to simulate a "headlamp" so that the mesh is always illuminated from the viewer's perspective. So the
I get the current orientation by applying BTW, maybe there is a more appropriate event than |
I won't have the possibility immediately so feel free to adapt it. Otherwise, I'll definitely have a look if it's still on later on. Just ping me for any questions. |
Nice, thanks. That makes sense. I updated it now to use the changed event from the transform. This should be the transform that is updated by the camera so it shouldn't do updates when not moving the camera and should keep the same amount of updates when moving the camera. I'll look at updating the filter tonight if I have time. |
I think I've updated the TextureFilter to make this work. I'm sure there is a better way to implement this that is more flexible, but I'm not sure this PR needs to be the place for it to become that flexible. @asnt @kmuehlbauer reviews are appreciated. |
Aw I liked the green cow 😉 White sounds good too I guess. @asnt Do you see anything else that needs to change with this? Ready to merge when the tests pass? |
:-) It was fun, indeed, but I thought for the demo it was better to let the texture intact. Not sure if really relevant, actually. Feel free to reset.
@djhoese This looks all good to me! Thanks for your support! @djhoese @kmuehlbauer @larsoner @campagnola |
I think it should be clear from the demo how to display a texture without altering its colour. Maybe a comment in the code would make this explicit: "white" = unaltered, "non-white" = blending of colour and texture. |
Agreed that it's good for the example to show this. If you want to update the docs to mention this and have it make sense then go for it. |
I added a little bit of documentation about the color. If we need more then let's add it in another PR. I've wasted enough of your time not merging these PRs. |
Wow this works great. It would be awesome to get this into a released version. |
The plan is for 0.7 this week. |
Oh, perfect. Great news. Thanks |
This PR implements texture support for MeshVisual as a filter.
Addresses #748
Edit(2018-11-05): Update following #1462 enforcing unindexed buffers for meshes.