What mechanism causes PlayCanvas to switch shaders(?) when opacity changes?

I’m trying to understand the internal PC mechanism for this behavior:

You can reproduce this live at Slither Stripe - Shaderfrog 2.0 Hybrid Graph Demo by moving the opacity slider on the blue data node.

The way this tool works:

  1. It takes all the graph properties (like opacity), and internally to build a throwaway PC material with all those properties set
  2. It forces a material compilation of its shader, by calling scene.render()
  3. It scrapes the generated GLSL from the PC generated
  4. It destroys the internal throwaway material, and keeps the GLSL.
  5. Then on the client / user facing component, it creates a new “live” pc.StandardMaterial(), and intercepts that material/shader’s GLSL and replaces it with the core GLSL the core system generated.

In the happy path, both the core and the userland shader are generated with the same properties. However, when changing the opacity slider, PlayCanvas clearly does something different per-frame if the material has opacity or not.

I’m still digging through PC’s guts to see what the switch here is, but figured I’d ask as well because it’s taking me longer than expected to find. What happens under the hood that causes PlayCanvas to use a separate shader(?) based on opacity? The PC material doesn’t have any other shader variations, there’s only one even though PC seems to be swapping out shader(s?) at runtime.

A related thought: For this tool to support possible multiple generated shaders, I might have to some coupling of the core of the tool to the possible variation options that can cause PC to generate multiple shaders. For example, if a shader is opaque, I would need to know that opacity generates two shader variations, and re-do the above steps proactively for both possible variations. I’m trying to avoid this kind of direct coupling to the engine plugin (PlayCanvas), so I’m trying to better understand the mechanism that can cause shader variations.

Hi @andyandy,

Not fully sure but this reminds that PlayCanvas automatically swaps out the opacity shader if opacity = default, 1.0 (=not required). @mvaligursky?

When you update material properties (opacity, textures and similar) and then call material.update(), we build an option object that describes what features the shader needs to render the material with new properties. If this option object is different, we’d generate new shader code for both vertex and fragment shader and use that instead of the previous shader.

So I think you need to hook into the way the new shader is created, and when that happens, you need to re-apply your processing as well (replace the shader). I added this shader:generate event recently that you should probably do all your processing inside.

1 Like

Thanks @mvaligursky. I’m not yet using that event, I’m still monkeypatching Playcanvas as that was working up until I discovered this opacity issue, looks like I need to try that event instead.

From my own debugging notes - I also found the switch is happening by swapping out mesh.render.meshInstances[0]._shader[0], and as you say, that happens because the generation string in program-library.js generates a different key if opacity is set. That key generation happens here: https://github.com/playcanvas/engine/blob/c0f794bca9e40e24d4fc65749ca39126436c792c/src/scene/shader-lib/program-library.js#L138 which takes into account if the material has opacity set on it or not.

@mvaligursky Thank you for adding the feature request, I am humbled that you added a feature to the core API as part of our discussion.

Now this tool lets you tween opacity!

Live: Hypnocube - Shaderfrog 2.0 Hybrid Graph Demo