Last active
October 6, 2024 19:21
-
-
Save partybusiness/d60f32317ed0a0548021dccbe304a9e8 to your computer and use it in GitHub Desktop.
Godot shader raycast FPS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#[compute] | |
#version 450 | |
// Invocations in the (x, y, z) dimension | |
layout(local_size_x = 8, local_size_y = 1, local_size_z = 1) in; | |
layout(rgba16f, set = 0, binding = 0) uniform restrict image2D distances; //distances to walls along each raycast, as well as data for wall texture | |
layout(rgba16f, set = 0, binding = 1) uniform restrict image2D map; //map to raycast on | |
layout(push_constant, std430) uniform Params { | |
float x; | |
float y; | |
float angle; | |
} view_pos; | |
//based on this: https://theshoemaker.de/2016/02/ray-casting-in-2d-grids/ | |
vec4 Intersect(vec2 startPos, vec2 forw) { | |
vec2 currentPos = startPos; | |
vec2 tile = floor(startPos)+1.0; | |
vec2 dTile = sign(forw); | |
vec2 dt = (tile + vec2(forw.x>0.0?0.0:-1.0, forw.y>0.0?0.0:-1.0) - startPos) / forw; | |
vec2 ddt = dTile / forw; | |
float t = 0; | |
if (length(forw)>0.0) { | |
while (t<64.0) { | |
if (dt.x < dt.y) { | |
tile.x = tile.x + dTile.x; | |
float sdt = dt.x; | |
t = t + sdt; | |
dt.x = dt.x + ddt.x - dt.x; | |
dt.y = dt.y - sdt; | |
} | |
else { | |
tile.y = tile.y + dTile.y; | |
float sdt = dt.y; | |
t = t + sdt; | |
dt.x = dt.x - sdt; | |
dt.y = dt.y + ddt.y - sdt; | |
} | |
vec4 col = imageLoad(map, ivec2(tile));// map.SampleLevel(samplermap, Pos2UV(tile), 0); | |
if (col.r > 0.1) { | |
vec2 pos = startPos + forw * t; | |
float tileU = (pos.x + pos.y) - floor(pos.x + pos.y); | |
return vec4(t, col.gb, tileU); //distance, colour data, and v for uv | |
} //else should we store empty tiles somewhere? | |
} | |
} | |
return vec4(512, 0,0,0); | |
} | |
void main() { | |
float fov = 0.5; //half of horizontal fov in radians | |
float numstrips = 512.0; //number of strips tested along the horizontal | |
float halfstrips = numstrips / 2.0; | |
float stripangle = gl_GlobalInvocationID.x / halfstrips - 1.0; //angle offset of this strip ranging from -1.0 to 1.0 | |
float angle = view_pos.angle + stripangle * fov; //combine viewing angle with angle of strip | |
vec2 forward = vec2(cos(angle), sin(angle)); | |
vec2 position = vec2(view_pos.x, view_pos.y); | |
imageStore(distances, ivec2(gl_GlobalInvocationID.x, 0), Intersect(position, forward)); | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
shader_type canvas_item; | |
render_mode unshaded; | |
// displays a first-person view based on distances calculated in find_distances.glsl | |
// texture of map, used for floor colour | |
uniform sampler2D map:filter_nearest, source_color; | |
// vertical strips calculated by the raycast compute shader | |
uniform sampler2D strips:filter_nearest; | |
// tiles displayed on any visible walls | |
uniform sampler2D wall_texture:source_color, filter_nearest; | |
// number of tiles across in wall_texture | |
// with value of 8, it's an 8x8 tile map | |
uniform int tile_count = 8; | |
// multiplier on distance when fading wall to black | |
uniform float fade_strength = 0.3; | |
// colour of ceiling | |
// this is just a single colour rather than sampling a texture like the floor does | |
uniform vec3 ceiling_colour:source_color; | |
// used to pass current camera position and direction to the display shader | |
uniform vec2 forward; | |
uniform vec2 position; | |
void vertex() { | |
// Called for every vertex the material is visible on. | |
} | |
float inverse_lerp(float a, float b, float c) { | |
return (a-c) / (a-b); | |
} | |
void fragment() { | |
// get info about current vertical strip | |
vec4 sampled = texture(strips,UV); | |
float dist = sampled.r; | |
float ypos = abs(UV.y - 0.5); | |
float wallHeight = (0.5 / dist); | |
float isWall = (ypos < wallHeight) ? 1.0: 0.0; | |
float B = (UV.x - 0.5); //horizontal fov in radians = 1.0, should match compute shader | |
// forward of | |
vec2 newForward = vec2(forward.x * cos(B) - forward.y * sin(B), forward.x * sin(B) + forward.y * cos(B)); //rotate vector by B | |
float yt = 1.0/(abs(ypos)*2.0);//how to project y angle ypos to distance to floor | |
vec2 floorCoord = position + newForward * yt; | |
float checkVal = float(int(round(floor(floorCoord.x) + floor(floorCoord.y))) % 2); | |
vec2 floorUV = (floorCoord + vec2(1.0)) / 512.0; // match size of map | |
vec4 floorColour = texture(map, floorUV); | |
vec3 noWallColour = mix(floorColour.rgb, ceiling_colour, (UV.y < 0.5)?1.0:0.0 ); | |
vec2 singleWallUv = vec2(sampled.a, inverse_lerp(0.5 - wallHeight, 0.5 + wallHeight, UV.y)); | |
vec2 wallUv = (floor(sampled.gb*float(tile_count)) + singleWallUv) / float(tile_count); //invlerp (0.5-wallHeight,0.5+wallHeight, yt); ?? | |
vec4 wallColour = texture(wall_texture, wallUv) / clamp((dist)*fade_strength,1,512); | |
vec4 result = mix(vec4(noWallColour,1.0), wallColour, isWall); | |
COLOR.rgb = result.rgb; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends ColorRect | |
class_name Renderer | |
var display_material:ShaderMaterial | |
var rd:RenderingDevice # = RenderingServer.create_local_rendering_device() | |
var buffer:RID | |
var ray_pipeline:RID | |
var ray_uniform_set:RID | |
var ray_calc_shader:RID | |
@export var pos:Vector2 | |
@export var angle:float | |
@export var rotation_speed:float = 1.5 | |
@export var walking_speed:float = 5.0 | |
var distances:Texture2DRD | |
var map:Texture2DRD | |
var block_map:Image | |
@export var map_source:Texture2D | |
var position_bytes:PackedByteArray | |
func _ready(): | |
rd = RenderingServer.get_rendering_device() | |
#set up position data | |
position_bytes.resize(16) | |
#set up map | |
#create texture | |
var map_texture : RDTextureFormat = RDTextureFormat.new() | |
#FORMAT_RGB8 | |
#DATA_FORMAT_R8G8B8_SINT | |
map_texture.format = RenderingDevice.DATA_FORMAT_R32G32B32A32_SFLOAT | |
map_texture.texture_type = RenderingDevice.TEXTURE_TYPE_2D | |
map_texture.width = map_source.get_width() | |
map_texture.height = map_source.get_height() | |
map_texture.depth = 1 | |
map_texture.array_layers = 1 | |
map_texture.mipmaps = 1 | |
map_texture.usage_bits = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT + RenderingDevice.TEXTURE_USAGE_COLOR_ATTACHMENT_BIT + RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT | |
#var source_rid = rd.texture_2d_create(map_source.get_data()) | |
map = Texture2DRD.new() | |
var source_map_image:Image = map_source.get_image() | |
source_map_image.convert(Image.FORMAT_RGBAF) | |
print(source_map_image.get_format()," = ",map_texture.format) | |
#map_texture.format = source_map_image.get_format() | |
var source_map_data:PackedByteArray = source_map_image.get_data() | |
print("blah",source_map_data.size()) | |
map.texture_rd_rid = rd.texture_create(map_texture, RDTextureView.new(), [source_map_data]) | |
var map_uniform = RDUniform.new() | |
map_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE | |
map_uniform.binding = 1 | |
map_uniform.add_id(map.texture_rd_rid) | |
#set up distances | |
var dis_texture : RDTextureFormat = RDTextureFormat.new() | |
dis_texture.format = RenderingDevice.DATA_FORMAT_R16G16B16A16_SFLOAT | |
# RenderingDevice.DATA_FORMAT_R16G16_SFLOAT | |
dis_texture.texture_type = RenderingDevice.TEXTURE_TYPE_2D | |
dis_texture.width = 512 | |
dis_texture.height = 1 | |
dis_texture.depth = 1 | |
dis_texture.array_layers = 1 | |
dis_texture.mipmaps = 1 | |
dis_texture.usage_bits = RenderingDevice.TEXTURE_USAGE_SAMPLING_BIT + RenderingDevice.TEXTURE_USAGE_COLOR_ATTACHMENT_BIT + RenderingDevice.TEXTURE_USAGE_STORAGE_BIT + RenderingDevice.TEXTURE_USAGE_CAN_UPDATE_BIT | |
distances = Texture2DRD.new() | |
distances.texture_rd_rid = rd.texture_create(dis_texture, RDTextureView.new(), []) | |
var distances_uniform = RDUniform.new() | |
distances_uniform.uniform_type = RenderingDevice.UNIFORM_TYPE_IMAGE | |
distances_uniform.binding = 0 | |
distances_uniform.add_id(distances.texture_rd_rid) | |
#set up distance calculator | |
ray_calc_shader = rd.shader_create_from_spirv(load("res://raycastFPS/find_distances.glsl").get_spirv()) | |
ray_uniform_set = rd.uniform_set_create([map_uniform,distances_uniform],ray_calc_shader,0) | |
ray_pipeline = rd.compute_pipeline_create(ray_calc_shader) | |
#set up display material | |
display_material = material | |
display_material.set_shader_parameter("map", map) | |
display_material.set_shader_parameter("strips", distances) | |
#await get_tree().process_frame | |
block_map = map_source.get_image() | |
func _process(delta): | |
#handle input to move | |
angle += Input.get_axis("Rotate_Left", "Rotate_Right") * delta * rotation_speed | |
var forward = Input.get_axis("Backward", "Forward") | |
var move_vector:Vector2 = Vector2(cos(angle), sin(angle)) * delta * forward * walking_speed | |
# do pixel test of map to see if there's a wall in the way | |
if block_map!=null: | |
var test_pos:Vector2i = Vector2i(ceil(pos.x + move_vector.x), ceil(pos.y + move_vector.y)) | |
var curr_pos:Vector2i = Vector2i(ceil(pos.x), ceil(pos.y)) | |
# if we're moving to a new pixel, check map for walls | |
if curr_pos.x != test_pos.x || curr_pos.y != test_pos.y: | |
var wall_check = block_map.get_pixel(test_pos.x,test_pos.y) | |
if wall_check.r > 0.1: | |
# there is a wall, now to decide whether to block along x or y axis | |
if curr_pos.x != test_pos.x && curr_pos.y != test_pos.y: | |
# corner case, test neighbours to pick direction | |
var wall_check1 = block_map.get_pixel(test_pos.x,curr_pos.y) | |
var wall_check2 = block_map.get_pixel(curr_pos.x,test_pos.y) | |
if wall_check1.r > 0.1: | |
move_vector.x = 0.0 | |
if wall_check2.r > 0.1: | |
move_vector.y = 0.0 | |
else: #simple cases | |
if curr_pos.x != test_pos.x: | |
move_vector.x = 0.0 | |
else: | |
move_vector.y = 0.0 | |
pos += move_vector | |
run_tick() | |
func run_tick(): | |
# assign position and forward to display shader | |
display_material.set_shader_parameter("forward", Vector2(cos(angle), sin(angle))) | |
display_material.set_shader_parameter("position", pos) | |
# set up compute shader | |
var view_pos:PackedFloat32Array | |
view_pos.resize(4) # needs to be multiple of 4 | |
view_pos[0] = pos.x | |
view_pos[1] = pos.y | |
view_pos[2] = angle | |
position_bytes = view_pos.to_byte_array() | |
var compute_list := rd.compute_list_begin() | |
rd.compute_list_bind_compute_pipeline(compute_list, ray_pipeline) | |
rd.compute_list_bind_uniform_set(compute_list, ray_uniform_set, 0) | |
rd.compute_list_set_push_constant(compute_list, position_bytes, 16) | |
rd.compute_list_dispatch(compute_list, 512, 1, 1) #tests 512 columns | |
rd.compute_list_end() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment