0 votes

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.

Godot version 3.4
in Engine by (65 points)

2 Answers

0 votes
Best answer

That was a hot minute :)
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:

ROOT NODE SCRIPT:

    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
                sphere_data.append_array([sphere_pos.x,sphere_pos.y
,sphere_pos.z,sphere_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:
            stream.clear()
            stream.put_float(i)
            stream.seek(0)
            for j in range(4):
                sphere_data_bytes.append(stream.get_8())
     """
         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

        get_tree().call_group("SCREENS","update_sphere_buffer",sphere_img1Tx)

SCREENS GDSCRIPT:

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

SCREEN SHADER CODE:

 ...
 /*
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);
    }
...

EDIT:
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.

by (65 points)
edited by
+2 votes

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: https://github.com/godotengine/godot-proposals/issues/931

by (27,898 points)

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.

Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.