Vav Labs
Back to case studies

Case study / Rendering architecture

SDF Raymarching in Godot 4

Author signed-distance fields with regular Node3D nodes, then raymarch their combined field in one fullscreen pass with smooth CSG, blended colors, and opaque depth integration.

Godot 4.7.1 stable ยท Forward+

Node-based SDF raymarcher in Godot showing a cyan sphere, coral box, gold torus, and violet subtractive box above an opaque display plinth

Quick answer: how this works in Godot

To raymarch SDFs in a Godot 4 scene, render a fullscreen spatial shader on aQuadMesh, reconstruct one world-space ray per pixel, evaluate the combined distance field, and clip the march against opaque scene depth. On a hit, write the projected SDF depth as well as the color.

The node-based edition adds one editor-facing layer: an@tool script collectsSdfShape3D nodes and uploads their transforms, primitive types, CSG operations, colors, and blend radii through fixed uniform arrays. If you don't need node authoring, the CC0 edition keeps the whole scene in one editablemap() function with no GDScript.

Choose the edition that matches the job

You needStart with
One shader to paste into an existing projectStandalone CC0 shader
Spheres, boxes, capsules, and tori authored in the SceneTreeNode-based MIT project
Live editor preview and automatic transform uploadsNode-based MIT project
A compact example you can rewrite from map() outwardStandalone CC0 shader

Both editions are free source. The standalone shader targets Godot 4.3+ and the complete node project targets Godot 4.7+. Both were tested on Godot 4.7.1 with Forward+.

The integration problem

SDF functions make procedural shapes, booleans, and smooth blends compact to describe. A self-contained shader demo can stop there. A tool inside a real Godot scene has to answer several less glamorous questions: where shapes are authored, how their transforms reach the shader, how operation order stays predictable, and what happens when an opaque mesh is in front of the field.

This implementation keeps that boundary small. Godot owns the scene and node transforms. One GDScript adapter packs shape data. One fullscreen spatial shader owns the distance-field evaluation, shading, and depth output.

Author SDF shapes as Node3D nodes

Each SdfShape3D is an@tool Node3D with six pieces of authoring data.

PropertyWhat it controls
shape_typeSphere, box, capsule, or torus
operationUnion, subtract, intersect, or a smooth variant
sizeRadius, half-extents, or torus radii
colourBase color used by the field and smooth blend
blend_strengthSmoothing radius for a smooth operation
sort_keyEvaluation order before the scene-tree tie-break

Move or rotate a node and the editor preview updates. Scale is interpreted by the primitive contract: boxes use per-axis scale, sphere radius uses its radius axis, and capsule radius uses an averaged axis scale while its segment follows the node axes. Those details are written down because "it follows transforms" isn't precise enough once non-uniform scale enters the scene.

Godot 4 editor with the BlendBox SdfShape3D node selected and its Smooth Union, size, color, and blend controls visible beside the live SDF viewport

Pack node state into a fixed shader contract

sdf_renderer.gd finds visible nodes in thesdf_shapes group every frame. It sorts them bysort_key, keeps scene-tree order as the tie-break, and uploads at most 32 entries through five arrays.

Uniform arrayPacked values
shp_pos_typeWorld position + primitive type
shp_size_opPrimitive size + CSG operation
shp_color_blendRGB color + blend radius
shp_customCapsule endpoint/radius or extra local rotation data
shp_rotInverse node rotation quaternion

Fixed uniform arrays keep the project easy to inspect and avoid adding a compute or storage-buffer path. They also make the limit visible. CSG order matters, sosort_key is public authoring data rather than an internal detail.

Raymarch the field in one fullscreen pass

The renderer pins a QuadMesh to the near plane and reconstructs a world-space ray from SCREEN_UV,INV_PROJECTION_MATRIX, andINV_VIEW_MATRIX. Godot 4.3+ uses reversed Z, so the fullscreen vertex uses a clip-space Z value of 1.0.

For each pixel, the shader sphere-traces the combined field until it hits the surface, reaches the step budget, leaves the permitted distance, or meets an opaque scene surface first. Four tetrahedral samples estimate the normal. The supplied lighting is a small Lambert-plus-highlight model driven by the scene'sDirectionalLight3D.

Silhouette coverage tracks the ray's closest approach relative to the pixel cone. That smooths the outer edge without rendering the scene several times. It's part of this shader's look, not a replacement for Godot's general anti-aliasing pipeline.

Opaque depth integration, with a transparent-pipeline limit

The shader samples hint_depth_texture and reconstructs the distance to the nearest opaque scene surface. If that surface is closer, the raymarch stops. If the SDF wins, the shader projects the hit point and writes its depth. On a miss it preserves the sampled scene depth, because Godot requires every branch to supply DEPTH once the shader writes it anywhere.

That gives the intended opaque composition: the display plinth can sit in front of part of the SDF field, while a nearer SDF surface can appear in front of the room geometry.

There is a separate transparency boundary. The shader writesALPHA for silhouette coverage, which puts the material in Godot's transparent pipeline. Overlapping transparent materials can still sort incorrectly, and this project doesn't claim to fix that renderer-wide problem.

Node-based SDF raymarcher in Godot showing a cyan sphere, coral box, gold torus, and violet subtractive box above an opaque display plinth

Smooth the color with the surface

A smooth union looks unfinished when the distance field blends but the color switches at a hard boundary. Here the smooth CSG helpers combine(color, distance) pairs. The interpolation that joins two surfaces also mixes their colors across the seam.

The validation scene makes that visible with a cyan sphere and coral box. Their authored source surfaces remain close but separate; the smooth-union region creates the bridge and carries the color transition through it. The torus stays an independent hard union, while the violet box demonstrates smooth subtraction.

What it costs

There is no published FPS number for this project. Runtime cost depends on the number of covered pixels, the number of sphere-tracing steps, and the amount of distance-field work evaluated inside each step. Adding a shape affects every marched pixel that reaches scene_sdf().

The project exposes max_steps, hitepsilon, maximum ray distance, and a 32-shape upload cap. Those are control points, not universal safe values. Profile the actual target scene and renderer. Until that measurement exists, the result is an implementation boundary rather than a performance promise.

What the project deliberately leaves out

The SDF surface doesn't generate collision geometry. You can parent anSdfShape3D under a moving or physics-driven node and the renderer will follow its transform, but collision still needs a separate authored shape.

It also doesn't inherit Godot mesh materials, native mesh shadows, reflections, or the engine's complete lighting model. Adding a new primitive still means adding its distance function and evaluation branch to the shader. This is a compact render path with a node authoring layer, not a replacement for the mesh pipeline or a visual material system.

Download the edition you need

MIT / Godot 4.7+

Node-based project

The SdfShape3D authoring node, renderer adapter, spatial shader, and the authored validation scene shown on this page.

CC0 / Godot 4.3+

Standalone shader

One editable .gdshader with the complete map(), smooth CSG color blending, depth integration, and no GDScript dependency.

The machine-readable receipt records the exact ZIP and standalone shader hashes, every file inside the archive, fifteen named checks, a clean extracted-project import, and the final main-scene smoke. It doesn't claim a frame-time result or renderer portability.

The node-based validation scene running: two RigidBody metaballs sprung to a slowly moving point, blending into the authored SDF cubes with color that carries across each seam.

Why there are two editions

The standalone file is useful when the shader itself is the project. Everything is in one place, the license is CC0, and changing the scene means changingmap().

The MIT project is for a different workflow. Its scene is authored from nodes, so transforms and operation order remain visible in the editor. That costs two small GDScript files and a fixed upload contract, but it removes the need to rewrite world-space positions inside shader code.

The related Unity URP SDF raymarcher case studykeeps its own narrower render-feature boundary. It doesn't use this node authoring layer, and this page doesn't treat the engines as interchangeable.

FAQ

How do you raymarch SDFs in Godot 4?

Use a fullscreen spatial shader on a QuadMesh, reconstruct the camera ray from screen coordinates, sphere-trace a combined signed distance field, and write the hit color and projected depth. For editor-authored shapes, collect Node3D data with an @tool script and upload it through fixed shader uniforms.

Which Godot version does this require?

The standalone CC0 shader targets Godot 4.3+ because it uses the reversed-Z fullscreen convention introduced in 4.3. The complete node-based project targets Godot 4.7+. Both editions were tested on Godot 4.7.1 with Forward+.

Do the raymarched shapes work with Godot scene depth?

Yes, for opaque scene geometry. The shader stops marching when sampled opaque depth is closer and writes projected SDF depth on a hit. Because it also writes ALPHA, overlapping transparent materials still have Godot's usual sorting limitations.

Can I move an SDF shape with a Node3D or physics body?

Yes. The node-based renderer reads each SdfShape3D global transform every frame, so parenting one under a moving node updates the rendered field. The included validation scene demonstrates this with two RigidBody3D metaballs and separately authored collision shapes; the SDF itself doesn't create collision geometry.

How many SDF shapes can the node project render?

The upload contract has a hard cap of 32 shapes. That's a data limit, not a performance guarantee. Screen coverage, march steps, and the cost of evaluating the field still determine runtime work.

Does this support the Compatibility or Mobile renderer?

Those renderers aren't supported targets for this release. The supplied projects were tested on Godot 4.7.1 Forward+. Porting the shader starts with checking depth texture, projection, precision, and performance behavior on the target backend.

Does the project create meshes, collisions, or native Godot materials?

No. It renders procedural surfaces in a custom spatial shader. Mesh conversion, collision generation, native mesh shadows, and standard Godot materials are outside this project's scope.

Sources, license, and related work

The node-based project is MIT-licensed. The standalone shader is CC0 1.0 Universal. Distance functions are based on Inigo Quilez's published reference.