Simple fullscreen raymarching

This shader uses the same method as in the tutorial for advanced post-processing in the documentation:
https://docs.godotengine.org/en/stable/tutorials/shading/advanced_postprocessing.html

To use it you need to add a quadmesh and set both its width and height to 2, and of course apply the shader to the material. To avoid culling when the camera isn´t pointing at the mesh, increase the extra_cull_margin under geometry to as large as possible. This way the shader works well in the editor also.

 

In Godot 4, check Flip Faces on the quad mesh and INV_VIEW_MATRIX instead of CAMERA_MATRIX in the shader code.

Shader code
shader_type spatial;
render_mode unshaded;

const int MAX_STEPS = 300;
const float MAX_DISTANCE = 1000.0;
const float MIN_DISTANCE = 0.001;

void vertex() {
	POSITION = vec4(VERTEX, 1.0);
}

float sdSphere (vec3 p, vec3 centrum, float radius) {
    return length(centrum-p) - radius;
}

// infinte repetitions
// adapted from https://www.iquilezles.org/www/articles/distfunctions/distfunctions.htm
vec3 opRep(vec3 p, vec3 c) {
    vec3 q = mod(p+0.5*c,c)-0.5*c;
    return q;
}

float get_distance(vec3 p) {
	//p = opRep(p, vec3(3)); // uncomment for repeating spheres
	return sdSphere(p, vec3(0), 1.0);
}

vec3 get_normal(vec3 p) {
	vec2 e = vec2(1e-3, 0);
	
	vec3 n = get_distance(p) - vec3(
		get_distance(p - e.xyy),
		get_distance(p - e.yxy),
		get_distance(p - e.yyx)
	);
	
	return normalize(n);
}

vec4 raymarch(vec3 ray_origin, vec3 ray_dir) {
	float t = 0.0;
	vec3 p = ray_origin;
	int i = 0;
	for (i = 0; i < MAX_STEPS; i++)
	{
		float d = get_distance(p);
		t += d;
		if (t > MAX_DISTANCE)
			break;
		
		p += d * ray_dir;
		if (abs(d)  < MIN_DISTANCE)
			return vec4(get_normal(p), 1);
 	}
	//return vec4(float(i) * 0.01); // uncomment for simple glow effect
	return vec4(0.0);
}

void fragment() {
	vec2 uv = SCREEN_UV * 2.0 - 1.0;
	vec4 camera = CAMERA_MATRIX * INV_PROJECTION_MATRIX * vec4(uv, 1, 1);
	
	vec3 ray_origin = CAMERA_MATRIX[3].xyz;
	vec3 ray_dir = normalize(camera.xyz);
	
	vec4 col = raymarch(ray_origin, ray_dir);
	
	ALPHA = col.a;
	ALBEDO = col.rgb;
}
Tags
raymarch, raymarching, SDF
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from kilojool

Raymarch in a box

Related shaders

Fullscreen Raymarching with Depth Check

Raymarching with depth writting

simple trail effect

Subscribe
Notify of
guest

5 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
abc defghijk
abc defghijk
3 years ago

I tried tens of times to find something like this, nearly succeeded in writing my own but had trouble figuring out the camera origin, and then searching for the solution to the camera origin problem turned up this page! Thanks!

lemon
lemon
9 months ago

this is cool and works well!, but I don’t necessarily understand how ray origin works? like what is at CAMERA_MATRIX[3] (or INV_VIEW_MATRIX[3] in case of godot 4)

ignawesome
ignawesome
6 months ago

This is exactly what I was looking for. Good to use as a base for other shaders. Now I just have to figure out how to actually make something that looks nice.

cdplayerptx
cdplayerptx
2 months ago

For anyone on 4.3, here’s the changes that should be made.

Add this to the top of your shader.

uniform sampler2D depth_texture : source_color, hint_depth_texture;

Change the top couple of lines of your fragment function to this.

void fragment() {
    float depth = texture(depth_texture, SCREEN_UV).x;
    vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
    vec4 camera = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1);

And change your vertex function to this for good measure:

void vertex() {
    POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}