we are currently working with some custom shaders that require a texture to be loaded from the server.
As we have multiple primitives that need the same texture, we’d like to be able to re-use the same resource for multiple
Additionally it’s required to change the texture at runtime.
We use something like this to set the value of the texture (after the custom shaders have already been created):
This works fine (although the docblock of the function doesn’t state that a
TextureUniform can be used as value here).
What we tried now is to just assign the same
TextureUniform to multiple shaders. However this will throw an error:
The Resource is already being fetched.
This happens because when adding an URL to the
Resource is created based of that URL and fetched when it needs to be loaded. So using the same
TextureUniform will obviously result in the above error, as the
TextureUniform’s resource is called to fetch the image multiple times.
I think, it’s fine to not be able to use the same
TextureUniform for two different shaders, as this seems a bit hacky anyways.
However, is there any way to use the same resource/the same fetched image as input for two different
We could just use two different
TextureUniforms with two different
Resources that fetch the same image. However, this seems a bit overkill, especially when more than two uniforms are involved.
I started to experiment with loading the image from the Server via a Cesium resource and then assigning it to a canvas etc. to be able to finally read the pixels from the image into a typed array and use this as input.
- didn’t work and I stopped trying, because
- it became quite ugly and I don’t think this is a really good way of doing this
If you want to try this out, here’s a sandcastle. Just uncomment the part after
// Using it for both models fails because the resource is fetched twice:
Thank you for bringing this to our attention. One question, you mentioned that you had
multiple primitives that need the same texture
Do these primitives use the same custom shader code, or is each one a different shader? If the former, you can just assign the same
CustomShader instance to multiple models, i.e.
const customShader = new Cesium.CustomShader(...);
modelA.customShader = customShader;
modelB.customShader = customShader;
Unfortunately, the latter case (one texture, multiple shaders) doesn’t currently have a way to reuse textures without duplicating memory at some point. You can certainly clone the resource (see
resource.clone()), but then you end up fetching the texture twice. Even if you got your typed array approach working, the internal code wold still create two separate textures in the GPU without some sort of caching in place.
I wrote up an issue about this with more details, you can find that here: Support reusing textures in multiple `CustomShader`s · Issue #10718 · CesiumGS/cesium · GitHub
While experimenting in the sandcastle I found the same thing yesterday (that using the same shader for two models doesn’t produce the problem), but I didn’t realize, that I might be able to use that. Now that you mention it again and I had another thought about it, this could actually work. I’ll definitely have another look at this, thank you!
The hint regarding the double copy of the texture on the GPU side also helps quite a lot, because this means that my other approach via a typed array wouldn’t make much sense anyways (at least it wouldn’t be very scalable ). I did not think about this yet, because I was still stuck at the CPU side.
So in general for me, the best way right now is to minimize shader count. This actually sounds pretty reasonable, I just have to admit, that it wasn’t clear to me until now, that this is how it works in the background. However, given what I know of Cesium’s code, this totally makes sense. Thanks for clarification and for creating an issue!
Glad to hear that reusing the
CustomShader works for your use case!
Yeah, from the public interface alone, it’s not obvious that the
CustomShader object can be reused. However, internally this sort of usage is quite common. Consider a 3D Tiles tileset like in this Sandcastle. Each tile content is a separate
Model instance, so when you set
tileset.customShader = new Cesium.CustomShader(...);, it propagates the custom shader to all the tiles in view automatically.