How to explicitly run shader on a mesh?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By toblin

As far as I understand, it’s not possible in 3.x to explicitly run a shader, for example by my_object.shader(). But, it should be possible to have a workaround by letting one frame proceed one step in the _process() function, for example by having some control variables.

Any ideas on how to best proceed with such a work-around? I need to load a shader with a texture input, run the shader once, and then store the output in the texture.

:bust_in_silhouette: Reply From: Zylann

What do you expect my_object.shader() to even do?
A shader runs on the GPU, when an object is drawn. You can’t execute a shader anytime you want (and expect what?), that’s not how things work. For a shader to run, a draw call must be issued to render on a given target.

I need to load a shader with a texture input, run the shader once, and then store the output in the texture.

This sounds more feasible. You can use a Viewport node with its own world and dimensions the same as the texture. It will render what’s chid of it, so if you want to run a shader over a texture, you can put a TextureRect as child, anchor it to fill the “viewport”, set the texture property, and then wait one frame for the viewport to render it (this may depend on this Viewport — Godot Engine (stable) documentation in English )

Then you can get the result next frame by getting viewport.get_texture().get_data().
It is not possible to force the render to happen immediately though.

Thanks very much for your feedback =). It is highly appreciated.

What do you expect my_object.shader() to even do?

Well… I’m not sure =). My naive guess was that the shader must work with some kind of initial data. So if a Spatial shader runs for the first time, say on a mesh, it must have some kind of input, such as vertices and colors for each vertex, that it then manipulates according to its code. And once it has run an iteration, it must somehow save that data for the next iteration. Otherwise it would just start from the same input every time, which seems inflexible. So I thought that I could provide it with some input somehow, execute the shader once, and then check how its data looks like after that. My initial expectations would be that I could have an object, such as my_object, that the shader would take as input by for example running my_object.shader() or similar on it.

My initial idea of how to provide input
My thought of how to provide the shader with a texture input would be to give it a uniform sample3D that I set as a texture3D from GDScript. Then I simply store the output data in the texture through the shader. Do you think that method would work?

Thoughts on your suggestion
I think I understand how to do what you propose. Let me collate to see that I’ve understood: 1) setup a Viewport with its own world, with the same dimensions as my texture. 2) Add a TextureRect as a child to the Viewport and set its texture property to my texture. This wiill be the input to the viewport. And 3) wait for the viewport to render one frame, and then grasp the texture’s data using viewport.get_texture().get_data().

So far I have two questions:

You can use a Viewport node with its own world and dimensions the same as the texture

How do I set the dimensions of the world? I look into the properties of viewport, such as its world and environment, but I don’t find anything connected to dimensions.

It is not possible to force the render to happen immediately though

Is it not possible to force the render through VisualServer.force_draw(…)?

toblin | 2020-09-11 07:36

My thought of how to provide the shader with a texture input would be to give it a uniform sample3D that I set as a texture3D from GDScript. Then I simply store the output data in the texture through the shader. Do you think that method would work?

You can do that with the viewport technique I mentionned. Create a ShaderMaterial with your shader inside, use set_shader_param to set the uniform parameters, and set that material on a TextureRect under the viewport (you may give it a dummy texture as well otherwise it might not draw anything). Wait a frame for it to render, and then get the viewport texture data, which will be a 2D RGBAH image.

How do I set the dimensions of the world? I look into the properties of viewport, such as its world and environment, but I don’t find anything connected to dimensions.

There is litterally a size property on Viewport: Viewport — Godot Engine (stable) documentation in English
I suggest you read the API docs, it even has links to tutorials.

Is it not possible to force the render through VisualServer.force_draw(…)?

This will re-render everything, not just your viewport.

Zylann | 2020-09-11 17:54

There is litterally a size property on Viewport

But the size property of the Viewport is only 2-dimensional. And I don’t see any way to set the dimensions of a World object. Since the goal is to:

[…] use a Viewport node with its own world and dimensions the same as the texture

where the texture has 3D data, how do I set the dimensions of the Viewport and its world to match that of 3D data?

Wait a frame for it to render, and then get the viewport texture data, which will be a 2D RGBAH image.

But I need a 3D texture as output. How do I get that from the viewport?

toblin | 2020-09-21 08:02

the size property of the Viewport is only 2-dimensional

Of course it is. Viewports are like screens, they are 2D rendering targets.

I need a 3D texture as output

Godot does not have an API to do volumetric render on an actual 3D texture. You can have 3D textures and modify them on CPU, but GPU render always goes to 2D. (and I feel the goal has shifted way further than your initial question so you may have to learn more about Godot’s rendering capabilities, and reconsider what your core problem really is).

Zylann | 2020-09-21 17:29

:bust_in_silhouette: Reply From: tshrpl

what you want is a compute shader, godot 3.x only supports vertex shader and fragment shader (fragment + light)