What's Godot's equivalent to a StructuredBuffer for shaders?

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

Hello, does anyone know an equivalent to “StructuredBuffers” that I can use to pass an array of vec3s and floats to a spatial shader? Imma use it to pass object properties (sphere radius, position, etc). I can’t just dedicate uniforms for each object property cause I’ll be using a LOT of objects.

:bust_in_silhouette: Reply From: Zylann

AFAIK this doesn’t exist yet in the Godot 3 API. Communicating with shaders is done only with uniform parameters, and arrays are not supported. The closest you can get is to provide a texture and sample its raw data without interpolation.
There has been requests to add support for array parameters: Support uniform arrays in the shader language · Issue #931 · godotengine/godot-proposals · GitHub

Thanks bro. But I think I figured it out. A guy at the discord channel named MathiasDrgon helped me understand how to pass an array of floats to a shader thru textures. Imma post the rest of it as an answer real quick.

19PHOBOSS98 | 2021-08-23 22:51

:bust_in_silhouette: Reply From: 19PHOBOSS98

That was a hot minute :slight_smile:
Big thanks to MathiasDrgon and MobiusTrap (2021) from Discord for breaking it down for me. I wrote my data in a texture and read it’s rgba channels in the shader.
Here’s the code I used:


    func update_RTX_sphere_pos_r():
    	var sphere_list_length = rb_spheres.size()

         Each point on an image has RGBA channels. This is where I store and read my data as floats. Since an image is created with 4 channels, might as well use the alpha channel (rgbA) and throw in my sphere's radius in there. First, I saved all of my floats to an array in-order. This makes it easier to iterate thru later in the shader.
    	var sphere_data = []
    	for r in rb_spheres:
    		var sphere_pos = r.global_transform.origin
    		var sphere_radius = r.radius
    	#print("sphere_data: ",sphere_data)
         To turn our saved data into an image we need to first convert 'sphere_data' into a PoolByteArray. I used StreamPeerBuffer to stream in my data as bytes.
    	var sphere_data_bytes = PoolByteArray([])
        #var sphere_data_bytes = PoolByteArray(sphere_data)#idk, I tried :)
    	var stream = StreamPeerBuffer.new()

    	for i in sphere_data:
    		for j in range(4):
         Here, I "create an image from data" saving 4 elements of "sphere_data" into the RGBA channels respectively (technically the sphere_data_byte is getting saved but  you know what I mean :) ). The first 2 parameters in "create_from_data" dictates the size of the image. That's how many cells (each with RGBA channels) we need to store our data.
         Say, I have 3 spheres that I want to mess with in my shader. Each with it's own position (vec3(floats)) value and radius (float). Lucky me, that perfectly adds up to 4 components (floats) that I can easily store in one cell (RGBA). I need 3 cells to store the data from 3 of my spheres. If I want to add more spheres I just need more cells in the image.
    	var sphere_img1 = Image.new()
    	var sphere_img1Tx = ImageTexture.new()

    	#sphere_img1.create_from_data(3,1,false, Image.FORMAT_RGBF, data_in_bytes)
#saves 3 components as rgba into one texel but the image would still use the alpha channel regardless so might as well use it.

    	sphere_img1.create_from_data(sphere_list_length,1,false, Image.FORMAT_RGBAF, sphere_data_bytes) 
#saves 4 components as rgba into one texel 

sphere_img1Tx.create_from_image(sphere_img1) #as I learned shaders can only take textures in a uniform so I turned the image into an ImageTexture


func update_sphere_buffer(sb):
    self.get_active_material(0).set_shader_param("sphere_buffer", sb)


inside the shader, there's a function called "texelFetch" that we can use to fetch a "texel"(texture pixel)(our cells). It returns a vec4 for each cell we point at (ivec2(x,y)).
Since I saved my data in a 3X1 image, I iterate through my texels with ivec2(s,0)
    uniform sampler2D sphere_buffer;
    //Instead of texture() I used texelFetch(), sparing me some math
    for (int s = 0; s < textureSize(sphere_buffer,0).x; s++){
        IntersectSphere(ray, bestHit, texelFetch(sphere_buffer,ivec2(s,0),0),vec3(0.20, 0.0, 0.80),vec3(0.0),0f);

Ok… for anyone interested, if you want speed and performance, fetching a texture in a loop each frame isn’t the best way to go. I found this. I learned that texture fetching is expensive and uniform arrays are faster. So, I guess I still need to wait for Godot.