Godot Version
Version 4.3
Question
So, for all of the years I’ve been working on games I’ve never had to actually post a question on a help forum, managing to scrape by on things I could scavenge on the corners of the internet. So as you can imagine I have reached quite a wall with this issue having had to go out of my way to make a reddit account.
Essentially, I’ve been trying to create a raymarching renderer for voxels with compute shaders, and although I would say I have a pretty thorough basis on godot and programming in general I am barely able to wrap my head around passing uniforms to compute shaders. I feel as though I am missing a lot, so apologies if this is a completely stupid issue that takes two seconds to fix. But I am at a complete loss and any insight would be massively appreciated.
My issue is not with making a raymarching renderer, but passing in the camera transform of all things. I have been able to pass in lots of values successfully in my finicking around, but with the code you’ll see below I pass in a vector3 representing the camera’s global_basis.z and attempt to receive it in the glsl shader (binding 4, set 0). Above the huge while loop at the very bottom (ignore that for now), you’ll see I’m returning the camera forward axis as a color to the screen. In the boiler plate you can also see that I’m inputting the value as Vector3(0, 0, 1), however, when I switch out the color being printed from the uniform variable to a hardcoded value of vec3(0.0, 0.0, 1.0) it renders a different color to that of the uniform.
I am at a complete loss as to why this could be happening, and it is obviously a huge issue for my game if the uniform is not being received properly by my gpu (I think its transmuting into an entirely different value somehow?). The only thing I’ve seen thrown around is the idea of shader caching issues, could that have something to do with it or is it a much simpler problem that I’m to blind to see?
Any help there is to offer would be so, so appreciated. Thank you for your time everyone!
Boilerplate:
extends CompositorEffect
class_name VoxelCompositor
var rendering_device:RenderingDevice
var shader:RID
var pipeline:RID
var depth_sampler:RID
var voxel_sampler:RID
func _notification(recieved:int) -> void:
if recieved == NOTIFICATION_PREDELETE:
if shader.is_valid():
RenderingServer.free_rid(shader)
if depth_sampler.is_valid():
RenderingServer.free_rid(depth_sampler)
if voxel_sampler.is_valid():
RenderingServer.free_rid(voxel_sampler)
func _init() -> void:
RenderingServer.call_on_render_thread(initialize_compute_shader)
func initialize_compute_shader() -> void:
rendering_device = RenderingServer.get_rendering_device()
var shader_file:RDShaderFile = load("res://Shaders/voxel_shader.glsl")
shader = rendering_device.shader_create_from_spirv(shader_file.get_spirv())
pipeline = rendering_device.compute_pipeline_create(shader)
func _render_callback(_callback_type:int, render_data:RenderData) -> void:
var scene_buffers:RenderSceneBuffersRD = render_data.get_render_scene_buffers()
if !scene_buffers: return
var dimensions:Vector2i = scene_buffers.get_internal_size()
var camera:Camera3D = GameMaster.player.get_node("CameraPivot/CameraArm/PlayerCamera")
var scene_data:RenderSceneData = render_data.get_render_scene_data()
var inverse_projection_matrix:Projection = scene_data.get_cam_projection().inverse()
var push_constants:PackedByteArray = PackedFloat32Array([dimensions.x, dimensions.y, inverse_projection_matrix[2].w, inverse_projection_matrix[3].w, 100, camera.fov, VoxelMaster.terrain_density, 0]).to_byte_array()
var scene_texture:RID = scene_buffers.get_color_layer(0)
var screen_uniform:RDUniform = RDUniform.new()
screen_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE
screen_uniform.binding = 0
screen_uniform.add_id(scene_texture)
var sampler_state:RDSamplerState = RDSamplerState.new()
sampler_state.min_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
sampler_state.mag_filter = RenderingDevice.SAMPLER_FILTER_LINEAR
depth_sampler = rendering_device.sampler_create(sampler_state)
var depth_texture:RID = scene_buffers.get_depth_layer(0)
var depth_uniform:RDUniform = RDUniform.new()
depth_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE
depth_uniform.binding = 1
depth_uniform.add_id(depth_sampler)
depth_uniform.add_id(depth_texture)
var voxel_image:Image = VoxelMaster.voxel_texture.get_image()
voxel_image.convert(Image.FORMAT_RGBA8)
var voxel_texture_format:RDTextureFormat = RDTextureFormat.new()
voxel_texture_format.width = voxel_image.get_width()
voxel_texture_format.height = voxel_image.get_height()
voxel_texture_format.usage_bits = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT
voxel_texture_format.format = RenderingDevice.DATA_FORMAT_R8G8B8A8_UINT
voxel_sampler = rendering_device.sampler_create(sampler_state)
var voxel_texture:RID = rendering_device.texture_create(voxel_texture_format, RDTextureView.new(), [voxel_image.get_data()])
var voxel_uniform:RDUniform = RDUniform.new()
voxel_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_SAMPLER_WITH_TEXTURE
voxel_uniform.binding = 2
voxel_uniform.add_id(voxel_sampler)
voxel_uniform.add_id(voxel_texture)
var world_buffer:RID = rendering_device.uniform_buffer_create(4, PackedInt32Array([GameMaster.loaded_world.world_seed]).to_byte_array())
var world_uniform:RDUniform = RDUniform.new()
world_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_UNIFORM_BUFFER
world_uniform.binding = 3
world_uniform.add_id(world_buffer)
var transform_byte_array:PackedByteArray = []
transform_byte_array.resize(32)
transform_byte_array.encode_var(0, Vector3(0, 0, 1))
transform_byte_array.encode_var(16, Vector3(0, 0, 0))
var transform_buffer:RID = rendering_device.uniform_buffer_create(transform_byte_array.size(), transform_byte_array)
var transform_uniform:RDUniform = RDUniform.new()
transform_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_UNIFORM_BUFFER
transform_uniform.binding = 4
transform_uniform.add_id(transform_buffer)
var voxel_uv_byte_array:PackedByteArray = VoxelMaster.voxel_uvs.to_byte_array()
var voxel_uv_buffer:RID = rendering_device.storage_buffer_create(voxel_uv_byte_array.size(), voxel_uv_byte_array)
var voxel_uv_uniform:RDUniform = RDUniform.new()
voxel_uv_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
voxel_uv_uniform.binding = 0
voxel_uv_uniform.add_id(voxel_uv_buffer)
var voxel_key_byte_array:PackedByteArray = VoxelMaster.voxel_uv_keys.to_byte_array()
var voxel_key_buffer:RID = rendering_device.storage_buffer_create(voxel_key_byte_array.size(), voxel_key_byte_array)
var voxel_key_uniform:RDUniform = RDUniform.new()
voxel_key_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_STORAGE_BUFFER
voxel_key_uniform.binding = 1
voxel_key_uniform.add_id(voxel_key_buffer)
var uniform_set:RID = UniformSetCacheRD.get_cache(shader, 0, [screen_uniform, depth_uniform, voxel_uniform, transform_uniform])
var array_set:RID = UniformSetCacheRD.get_cache(shader, 1, [voxel_uv_uniform, voxel_key_uniform])
var compute_list:int = rendering_device.compute_list_begin()
rendering_device.compute_list_bind_compute_pipeline(compute_list, pipeline)
rendering_device.compute_list_bind_uniform_set(compute_list, uniform_set, 0)
rendering_device.compute_list_bind_uniform_set(compute_list, array_set, 1)
rendering_device.compute_list_set_push_constant(compute_list, push_constants, push_constants.size())
rendering_device.compute_list_dispatch(compute_list, dimensions.x / 16 + 1, dimensions.y / 16 + 1, 1)
rendering_device.compute_list_end()
Compute Shader (glsl):
#[compute]
#version 450
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
layout(rgba16f, binding = 0, set = 0) uniform image2D screen_texture;
layout(binding = 1, set = 0) uniform sampler2D depth_texture;
layout(binding = 2, set = 0) uniform sampler2D voxel_texture;
layout(binding = 4, set = 0) uniform restrict Transform {
vec3 camera_position;
vec3 camera_forward;
} transform;
layout(binding = 0, set = 1, std430) buffer Voxel_UVs {
vec2 uv_array[];
} voxel_uvs;
layout(binding = 1, set = 1, std430) buffer Voxel_Keys {
vec3 key_array[];
} voxel_keys;
layout(push_constant, std430) uniform Parameters {
vec2 screen_dimensions;
float inverse_projection_matrix_2w;
float inverse_projection_matrix_3w;
float view_distance;
float FOV;
float terrain_density;
} params;
ivec3 get_voxel(vec3 position) {
float terrain_density = params.terrain_density;
vec2 terrain_position = vec2(round(position.x / terrain_density) * terrain_density, round(position.z / terrain_density) * terrain_density);
vec2 terrain_feature_positions[3];
terrain_feature_positions[0] = terrain_position;
terrain_feature_positions[1] = vec2(terrain_position.x, terrain_position.y + sign(position.y - terrain_position.y) * terrain_density);
terrain_feature_positions[2] = vec2(terrain_position.x + sign(position.x - terrain_position.x) * terrain_density, terrain_position.y);
ivec3 terrain_feature_IDs[3];
terrain_feature_IDs[0] = ivec3(0, 0, 0);
terrain_feature_IDs[1] = ivec3(0, 0, 0);
terrain_feature_IDs[2] = ivec3(0, 0, 0);
float terrain_feature_weights[3];
terrain_feature_weights[0] = length(abs(vec2(position.x, position.y) - terrain_feature_positions[0]));
terrain_feature_weights[1] = length(abs(vec2(position.x, position.y) - terrain_feature_positions[1]));
terrain_feature_weights[2] = length(abs(vec2(position.x, position.y) - terrain_feature_positions[2]));
ivec3 result_IDs[3];
for (int feature = 0; feature < 3; feature += 1) {
ivec3 feature_ID = terrain_feature_IDs[feature];
if (feature_ID == ivec3(0, 0, 0)) {
if (position.y < 10.0) {
result_IDs[feature] = ivec3(0, 0, 0);
}
else {
result_IDs[feature] = ivec3(0, -1, 0);
}
}
}
ivec3 final_result_ID = ivec3(0, -1, 0);
vec3 result_weights;
for (int result = 0; result < 3; result += 1) {
if (result_IDs[result].x == final_result_ID.x) {
result_weights.x += terrain_feature_weights[result];
}
else if (terrain_feature_weights[result] > result_weights.x) {
final_result_ID.x = result_IDs[result].x;
result_weights.x = terrain_feature_weights[result];
}
if (result_IDs[result].y == final_result_ID.y) {
result_weights.y += terrain_feature_weights[result];
}
else if (terrain_feature_weights[result] > result_weights.y) {
final_result_ID.y = result_IDs[result].y;
result_weights.y = terrain_feature_weights[result];
}
if (result_IDs[result].z == final_result_ID.z) {
result_weights.z += terrain_feature_weights[result];
}
else if (terrain_feature_weights[result] > result_weights.z) {
final_result_ID.z = result_IDs[result].z;
result_weights.z = terrain_feature_weights[result];
}
}
return final_result_ID;
}
vec4 get_pixel(vec2 position, ivec3 normal, ivec3 voxel_ID) {
ivec2 voxel_uv = ivec2((position - floor(position)) / 32.0);
int voxel_index;
for (int i = 0; i < voxel_uvs.uv_array.length(); i += 1) {
if (voxel_keys.key_array[i] == voxel_ID) {
voxel_index = i;
break;
}
}
return texture(voxel_texture, voxel_uvs.uv_array[voxel_index] + voxel_uv);
}
void main() {
//Get Fragment Pixel and UV
ivec2 pixel_position = ivec2(gl_GlobalInvocationID.xy);
vec2 screen_dimensions = params.screen_dimensions;
if (pixel_position.x >= screen_dimensions.x || pixel_position.y >= screen_dimensions.y) return;
vec2 uv = pixel_position / screen_dimensions;
//Calculate Depth of Pixel on Main Renderer
float depth = texture(depth_texture, uv).r;
float linear_depth = 1.0 / (depth * params.inverse_projection_matrix_2w + params.inverse_projection_matrix_3w);
linear_depth = clamp(linear_depth / params.view_distance, 0.0, 1.0);
//Calculate Ratios Between Screen and Fragment
float aspect_ratio = params.screen_dimensions.x / params.screen_dimensions.y;
float view_angle = radians(params.FOV) / 2.0;
vec2 screen_coordinate = vec2((uv.x - 0.5) * 2.0 * aspect_ratio, (uv.y - 0.5) * -2.0);
vec2 offsets = screen_coordinate * tan(view_angle);
//Calculate Direction of Ray to Raymarch
vec3 ray_z = normalize(vec3(0.0, 0.0, 1.0));
vec3 ray_x = cross(ray_z, normalize(vec3(0.0, 1.0, 0.0)));
vec3 ray_y = cross(ray_x, ray_z);
vec3 ray_direction = normalize(vec3(ray_z + ray_x * offsets.x + ray_y * offsets.y));
//Raymarch Ray
vec3 ray_start = transform.camera_position;
vec3 ray_end = ray_start + ray_direction * params.view_distance;
vec3 ray_position = ray_start;
vec3 ray_travelled = vec3(0.0, 0.0, 0.0);
vec3 sign_direction = sign(ray_direction);
vec3 alternate = (transform.camera_forward + 1.0) / 2.0;
imageStore(screen_texture, pixel_position, vec4(alternate, 1.0));
return;
while (true) {
ivec3 next_voxel_on_axis = ivec3(abs(floor(ray_position * sign_direction)) * sign(ray_position) + vec3(1.0, 1.0, 1.0) * sign_direction);
vec3 distances = vec3(abs(vec3(next_voxel_on_axis) - ray_position) / abs(ray_direction));
if ((distances.x <= distances.y || isinf(distances.y) || isnan(distances.y)) && (distances.x <= distances.z || isinf(distances.z) || isnan(distances.y))) {
ray_position += ray_direction * distances.x;
ray_travelled += ray_direction * distances.x;
if (length(abs(ray_travelled)) >= length(abs(ray_start - ray_end))) {
break;
}
ivec3 voxel = get_voxel(floor(ray_position));
if (voxel.y != -1) {
vec4 pixel_color = get_pixel(vec2(ray_position.y, ray_position.z), ivec3(0, 0, 0), voxel);
imageStore(screen_texture, pixel_position, pixel_color);
break;
}
}
if ((distances.y <= distances.x || isinf(distances.x) || isnan(distances.x)) && (distances.y <= distances.z || isinf(distances.z) || isnan(distances.z))) {
ray_position += ray_direction * distances.y;
ray_travelled += ray_direction * distances.y;
if (length(abs(ray_travelled)) >= length(abs(ray_start - ray_end))) {
break;
}
ivec3 voxel = get_voxel(floor(ray_position));
if (voxel.y != -1) {
vec4 pixel_color = get_pixel(vec2(ray_position.x, ray_position.z), ivec3(0, 0, 0), voxel);
imageStore(screen_texture, pixel_position, pixel_color);
break;
}
}
if ((distances.z <= distances.x || isinf(distances.x) || isnan(distances.x)) && (distances.z <= distances.y || isinf(distances.y) || isnan(distances.x))) {
ray_position += ray_direction * distances.z;
ray_travelled += ray_direction * distances.z;
if (length(abs(ray_travelled)) >= length(abs(ray_start - ray_end))) {
break;
}
ivec3 voxel = get_voxel(floor(ray_position));
if (voxel.y != -1) {
vec4 pixel_color = get_pixel(vec2(ray_position.x, ray_position.y), ivec3(0, 0, 0), voxel);
imageStore(screen_texture, pixel_position, pixel_color);
break;
}
}
}
}