Broken Shadows from Directional Lights and SDF/Raymarching

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By kastenbutt

Hello everyone!

I’ve been experimenting with Godot and raymarching and hit upon a problem. Here is a scene:


The red sphere is created by my raymarching shader (the base geometry is a cube), the blue sphere is a standard sphere mesh from godot. As you can see the shadow of the red sphere is broken.

The shadow is cast by a directional light. So my first guess was that the broken shadow is due to the fact that I assume a perspective projection for the camera rather than an orthogonal (which is used for rendering the shadow map for the directional light). So I accounted for that in my shader and as you can see in the next screenshot this was not the problem after all. There the sphere itself is rendered correctly with an orthographic camera.

Orthogonal Camera

Here is a screenshot from RenderDoc that shows the shadow map / depth texture of the directional light:

Shadow map of directional light

As you can see, the red sphere is not rendered correctly. So, I assume I’m missing something in my shader, but I have no idea what it is. Does anyone have an idea where the problem could arise?

Here is the shader code:

shader_type spatial;

const int MAX_MARCHING_STEPS = 1000;
const float MAX_DEPTH = 1000.0;
const float NO_HIT = -1.0;
const float EPSILON = 0.00001;
const float N_EPSILON = 0.001;

float sdfSphere(vec3 p, vec3 pos, float radius) {
    return length(pos - p) - radius;

float sdfScene(vec3 p) {
	return sdfSphere(p, vec3(0.0, 0.0, 0.0), 1.0);

float raymarch(vec3 rayStart, vec3 viewRayDirection, float startDepth) {
    float depth = startDepth;
    for (int i = 0; i < MAX_MARCHING_STEPS; i++) {
        float dist = sdfScene(rayStart + depth * viewRayDirection);
        if (dist < EPSILON) {
            return depth;
        depth += dist;

        if (depth >= MAX_DEPTH) {
            return NO_HIT;
    return NO_HIT;

vec3 inferNormal(vec3 surfacePosition) {
    return normalize(
            sdfScene(surfacePosition + vec3(N_EPSILON, 0.0, 0.0)),
            sdfScene(surfacePosition + vec3(0.0, N_EPSILON, 0.0)),
            sdfScene(surfacePosition + vec3(0.0, 0.0, N_EPSILON))
        ) - vec3(
            sdfScene(surfacePosition - vec3(N_EPSILON, 0.0, 0.0)),
            sdfScene(surfacePosition - vec3(0.0, N_EPSILON, 0.0)),
            sdfScene(surfacePosition - vec3(0.0, 0.0, N_EPSILON))

varying vec3 worldPosition;
varying flat mat4 modelViewMatrix;
void vertex() {
	worldPosition = VERTEX;
	modelViewMatrix = MODELVIEW_MATRIX;

void fragment() {
	bool isOrthographic = PROJECTION_MATRIX[3].w == 1.0;
	vec3 rayStart;
	if (isOrthographic) {
		vec4 clipSpacePosition;
		clipSpacePosition.xy = ((FRAGCOORD.xy / VIEWPORT_SIZE.xy) * 2.0) - vec2(1.0, 1.0); = vec2(-1.0, 1.0);
		rayStart = (CAMERA_MATRIX * INV_PROJECTION_MATRIX * clipSpacePosition).xyz;
	} else {
		rayStart = CAMERA_MATRIX[3].xyz;
	vec3 direction = worldPosition - rayStart;
	float startDepth = length(direction);
    direction = normalize(direction);
    float depth = raymarch(rayStart, direction, startDepth);
    if (depth == NO_HIT) {
    vec3 surfacePosition = rayStart + depth * direction;
	vec4 projectedPos = PROJECTION_MATRIX * modelViewMatrix * 
            vec4(surfacePosition, 1.0);
	NORMAL = normalize(
        (modelViewMatrix * vec4(inferNormal(surfacePosition), 0.0)).xyz
	ALBEDO = vec3(1.0, 0.0, 0.0);
	DEPTH = 0.5 * (projectedPos.z / projectedPos.w) + 0.5;

By the way: Shadows of OmniLights work perfectly.

:bust_in_silhouette: Reply From: kastenbutt

I found the problem: In the renderpass for the shadow mapVIEWPORT_SIZE is not set to the actual viewport size (which is 4096x4096) but remains the same as for the main render pass. This is a bug in the engine I’d say.

For the record, this issue was reported on GitHub: VIEWPORT_SIZE has wrong values in Shadow Map Render Pass · Issue #51546 · godotengine/godot · GitHub

Calinou | 2021-08-12 20:26