How to define data in a Texture3D? Using Image not working

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

I’m working with shaders, where I want to provide a Texture3D as input to the shader. To do so I need to define the data in the Texture3D. But I’m having troubles doing so.

First I create my image, and set the value of its only pixel to 0.7, 0,0,0:

var image = Image.new()
image.create(1, 1, false, Image.FORMAT_RGB8)
image.lock()
image.set_pixel( 0,0, Color(0.7, 0.0, 0.0, 0.0))

When I check the pixel value, it seems to have worked, as image.get_pixel(0,0) gives me 0.698039,0,0,1.

Then I try to use the image to define data in a Texture3D

var texture = Texture3D.new()
texture.create( 1, 1, 1, Image.FORMAT_RGB8 )
texture.set_layer_data(image, 1)

But when I check the pixel value, it has suddenly changed. texture.data['layers'][0].get_pixel(0,0)) gives 0,0,0,1 as output. And it doesn’t seem to work to set the pixel value after it has been entered into the texture:

texture.data['layers'][0].lock()
texture.data['layers'][0].set_pixel(0,0, Color(0.7, 0.0, 0.0, 0.0))

does not work as it gives the error set_pixel: Image must be locked with 'lock()' before using set_pixel().

So, why isn’t the above working? And a more open question is: how would one properly define the data in a Texture3D?

Shouldn’t it also be texture.set_layer_data(image, 0)?

Tim Martin | 2020-09-09 12:38

You are correct! I came to the same conclusion independently =). Should have read your comment earlier xD.

toblin | 2020-09-11 07:50

:bust_in_silhouette: Reply From: Zylann

I never created a Texture3D from code before, but I’m not sure if data is meant to be used in this way.

texture.data['layers'][0].lock()
texture.data['layers'][0].set_pixel(0,0, Color(0.7, 0.0, 0.0, 0.0))

Sounds like data returned you a copy of the data (and not just one layer, the WHOLE data). So you locked the copy, but then set_pixel fails because it’s another copy you are getting.

For comparison, the same happens if you do this with a regular texture, for example:

texture.get_data().lock() # Returns a COPY (which is immediately dropped)
texture.get_data().set_pixel(...) # Returns ANOTHER COPY (so wont work)

Besides, even if modifying the pixel in the image worked, it won’t update the texture on your graphics card. Only the image the data property will have downloaded from it.

If you want to modify pixels of a Texture3D, you have to re-upload layers the same way as you did at the beginning. To update only a sub-region of it, maybe you can use set_data_partial: TextureLayered — Godot Engine (stable) documentation in English

Besides, even if modifying the pixel in the image worked, it won’t update the texture on your graphics card.

My plan was to provide the Texture3D as input to the shader through a uniform variable. So all of the above is an attempt to create the input to the shader before passing it to the GPU. I’ve asked whether or not that is a good idea in a comment to your reply here: How to explicitly run shader on a mesh? - Archive - Godot Forum

If you want to modify pixels of a Texture3D, you have to re-upload layers the same way as you did at the beginning

My major point is that this doesn’t seem to work. I create the image, set the pixel values I want, but then after I use that image to define data in the texture, the data in the texture is not what I defined in the image.

Ok - I found the problem while typing the above. The problem was that I tried to set the data on the wrong layer:

texture.set_layer_data(image, 1)  # <-------- incorrect, since index of layer is 0
texture.set_layer_data(image, 0)  # <-------- correct

toblin | 2020-09-11 07:49

:bust_in_silhouette: Reply From: toblin

As Tim Martin pointed out, the problem was that I tried to set partial data on the wrong layer

texture.set_layer_data(image, 1)  # incorrect, since index of layer is 0
texture.set_layer_data(image, 0)  # correct