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:


    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.

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 (28,833 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 Frequently asked questions and 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.