|
|
|
|
Reply From: |
19PHOBOSS98 |
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.