# How do you integrate Signed Distance Fields (SDF) into a basic three.js scene?

**URL:** <https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504>\
**Category:** Questions\
**Tags:** sdf\
**Created:** [February 3, 2025, 5:24pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504 "2025-02-03T17:24:02Z")\
**Posts on this page:** 9\
**Page:** 2

<div class="post-metadata">

**Author:** ![dubois](https://avatars.discourse-cdn.com/v4/letter/d/e9c0ed/32.png) [@dubois](https://discourse.threejs.org/u/dubois)\
**Post date:** [February 13, 2025, 2:27pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/21 "2025-02-13T14:27:32Z")

</div>

TIL gl\_DepthRange. Wow! How long has this been available?

---

<div class="post-metadata">

**Author:** ![hofk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/hofk/32/44944_2.png) [@hofk](https://discourse.threejs.org/u/hofk)\
**Post date:** [February 13, 2025, 2:29pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/22 "2025-02-13T14:29:11Z")

</div>

Hurray! it works. 🙂

Link overwritten with improved file.

* * *

Annotation:

If I see it correctly, the last value for gl\_FragColor is the transparency. In the current version r173 you can reduce the value 1.0 and get a brighter display, at 0 .0 it was then only white.

In the version up to r136 it doesn’t matter what number is there, it is obviously discarded.

There must have been an internal change in three.js ?

---

<div class="post-metadata">

**Author:** ![hofk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/hofk/32/44944_2.png) [@hofk](https://discourse.threejs.org/u/hofk)\
**Post date:** [February 15, 2025, 7:00pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/23 "2025-02-15T19:00:30Z")

</div>

I have now generated a variant of the integration of the SDFs into the three.js scene, which I like quite a bit.

[06\_SDF-Shader-Raymarching](https://hofk.de/main/threejs/_SDF/06_SDF-Shader-Raymarching.html)

But there is one strange thing when you call up or reload the page. The position of the SDFs is shifted and a piece is a bit off to the side. If you move the scene briefly (OrbitControls), the SDF jumps to the correct position and it is perfect.

 ![2025-02-15 18.34.26](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/1/8/18a25336f8774c98f107ab41c5763c8a1a5a2467.png)

 ![2025-02-15 18.34.45](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/d/f/df60fac2c1994645b6a07678d95cac481ffa944c.png)

**UPDATE:**  
found 🙃

```javascript
camPos.copy(camera.position);
cube.worldToLocal(camPos);
shaderMaterial.uniforms.camPos.value.copy(camPos);

```

then  
`renderer.setAnimationLoop( () => { renderer.render(scene, camera);});`

* * *

The code:

```javascript
<!DOCTYPE html>
 
<head>
  <title>06_SDF-Shader-Raymarching</title>
  <meta charset="utf-8" />
<style>
	body{
	overflow: hidden;
	margin: 0;
	}  
 </style>
</head>

<body></body>

<script type="module">

import * as THREE from "../jsm/three.module.173.js";
import { OrbitControls } from "../jsm/OrbitControls.173.js";

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 65, window.innerWidth / window.innerHeight, 0.1, 1000 );
const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setClearColor(0xdedede);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera.position.set(0, 2, 4);
//controls
const controls = new OrbitControls(camera, renderer.domElement);
//controls.zoomSpeed = 2;
//controls.maxPolarAngle = Math.PI * 0.5;
//controls.update();

const axesHelper = new THREE.AxesHelper( 10 );
scene.add( axesHelper );
const gridHelper = new THREE.GridHelper( 10, 10 );
scene.add( gridHelper );

const sphMesh = new THREE.Mesh(new THREE.SphereGeometry(0.75, 12, 12), new THREE.MeshBasicMaterial({color: 0xff0000, wireframe: true}));
scene.add( sphMesh );
sphMesh.position.set( 1, 1, -1);

const define_SDFs = `
  float sdSphere( vec3 p, float radius ) {
    return length( p ) - radius;
  }
     
  float sdTorus( vec3 p, vec2 t ){
    return length( vec2(length(p.xz)-t.x,p.y) )-t.y;
  }
  
  float sdRoundBox( vec3 p, vec3 b, float r ){ // r rounding
    vec3 q = abs(p) - b + r;
    return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
  }
 
  float sdBoxFrame( vec3 p, vec3 b, float e ){
      p = abs(p)-b;
      vec3 q = abs(p+e)-e;
      return min(min(
      length(max(vec3(p.x,q.y,q.z),0.0))+min(max(p.x,max(q.y,q.z)),0.0),
      length(max(vec3(q.x,p.y,q.z),0.0))+min(max(q.x,max(p.y,q.z)),0.0)),
      length(max(vec3(q.x,q.y,p.z),0.0))+min(max(q.x,max(q.y,p.z)),0.0));
  }`;

const designSDFs = `

 distCol GetDist(in vec3 p) {
  
  distCol dc;
  
  distCol dcSph;
  dcSph.d = sdSphere(p, 0.5 );
  dcSph.c = vec3( 0.6, 0.6, 0.1 );
  
  distCol dcTor;
  dcTor.d = sdTorus(p, vec2(0.575, 0.14));
  dcTor.c = vec3( 0.5, 0.1, 0.5 );
  
  //dc = opUnion(dcSph, dcTor); // perform operations separately ?
  //.............................................
  float d = min(dcSph.d, dcTor.d);
  vec3 c = d < dcTor.d ? dcSph.c : dcTor.c; 
  //.............................................
  
  /*
  float dRBox = sdRoundBox(p, vec3(1.9, 0.05, 0.2), 0.02); // see boxGeometry definition: 2,2,2
  float dBoxFrm = sdBoxFrame(p, vec3(1.0, 0.1, 0.2), 0.02);
  
  float d = min(max(-dSph, dTor), dRBox); // = SDF Union( SDF Subtraction )
  float d = min(max(-dSph, dTor), dBoxFrm); // = SDF Union( SDF Subtraction )
  */

   dc.d = d;
   dc.c = c;
  return dc;    
}`
    
const vShader = `
  varying vec3 vPosition;
  varying vec2 vUv;
  void main() {
  	vPosition = position;
  	vUv = uv;
  	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`;

const fShader = `
  uniform vec3 camPos;
  varying vec3 vPosition;
  varying vec2 vUv;
  #define MAX_STEPS 250
  #define MAX_DIST 100.
  #define SURF_DIST 1e-4
  #define PI 3.1415926
  
  struct distCol { // distance, color
    float d;
    vec3 c;
  };
  
  mat2 Rot(float a) {
  float s = sin(a), c = cos(a);
  return mat2(c, -s, s, c);
  }
  vec2 opMin(vec2 a, vec2 b){
    return a.x < b.x ? a : b;
  } 
  ` 
  +
    define_SDFs
  +
    designSDFs
  +
  `
  distCol RayMarch(vec3 ro, vec3 rd) {
  
    distCol dc;
    float dO = 0.;
  	for(int i = 0; i < MAX_STEPS; i++) {
        vec3 p = ro + rd*dO;
        dc = GetDist(p);
        dO += dc.d;
  		if(dO > MAX_DIST || dO < SURF_DIST) break;
  	}
    dc.d = dO;
  	return dc;
  }

  vec3 GetNormal(vec3 p) {
  	float d = GetDist(p).d;
  	vec2 e = vec2(SURF_DIST, 0);
    float d1 = GetDist(p-e.xyy).d;
    float d2 = GetDist(p-e.yxy).d;
    float d3 = GetDist(p-e.yyx).d;
  	vec3 n = d - vec3(d1, d2, d3);
  	return normalize(n);
  }
 
  float GetAo(vec3 p, vec3 n) {
  	float occ = 0.;
  	float sca = 1.;
  	for(int i = 0; i < 5; i++) {
  		float h = 0.001 + 0.15*float(i)/4.0;
  		float d = GetDist(p+h*n).d;
  		occ += (h-d)*sca;
  		sca *= 0.95;
  	}
  	return clamp( 1.0 - 1.5*occ, 0.0, 1.0 );
  }

  float GetLight(vec3 p, vec3 lPos) {
  	vec3 l = normalize(lPos-p);
  	vec3 n = GetNormal(p);
  	float dif = clamp(dot(n, l), 0., 1.);
  	return dif;
  }

  void main() {
  	vec2 uv = vUv-.5;
  	vec3 ro = camPos;
  	vec3 rd = normalize(vPosition - ro);
    distCol dc = RayMarch(ro, rd);
    if(dc.d >= MAX_DIST) {
        gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); // no hit
    } else {
        vec3 col;
        vec3 p = ro + rd * dc.d;
        vec3 lightPos = vec3(2, 16, 3);
        vec3 dir = vec3(GetLight(p, lightPos));
        vec3 indir = vec3(.051*GetAo(p, GetNormal(p)));
        
        col = 1.1*dc.c + 0.5*dir + 0.4*indir;
        //col = mix(vec3(0.870588235), col, 0.9); // #dedede rgb(222,222,222)

        //gl_FragColor = vec4( col, 0. ); // works up to r136
        gl_FragColor = vec4( col, 1. ); // also 136 and later up to 173 ... use also 0.9 0.8 ...
    }    
  }
  `;

const boxGeometry = new THREE.BoxGeometry(2, 2, 2);
 
const shaderMaterial = new THREE.ShaderMaterial({
  uniforms: {camPos: {value: new THREE.Vector3().copy(camera.position)}},
  vertexShader: vShader,
  fragmentShader: fShader,
  side: THREE.DoubleSide,
  transparent: true
});
const cube = new THREE.Mesh(boxGeometry, shaderMaterial);
scene.add(cube);
cube.position.set( 2, 1, -1 );

let camPos = new THREE.Vector3();

controls.addEventListener("change", event => {
  camPos.copy(camera.position);
  cube.worldToLocal(camPos);
  shaderMaterial.uniforms.camPos.value.copy(camPos);
}, false);
 
// ...UPDATE...
camPos.copy(camera.position);
cube.worldToLocal(camPos);
shaderMaterial.uniforms.camPos.value.copy(camPos);
//.....

renderer.setAnimationLoop( () => { renderer.render(scene, camera);});

</script>
</html>

```

---

<div class="post-metadata">

**Author:** ![PavelBoytchev](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/pavelboytchev/32/38639_2.png) [@PavelBoytchev](https://discourse.threejs.org/u/PavelBoytchev)\
**Post date:** [February 15, 2025, 7:13pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/24 "2025-02-15T19:13:48Z")

</div>

Any plans for animation of SDF objects?

---

<div class="post-metadata">

**Author:** ![prisoner849](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/prisoner849/32/535_2.png) [@prisoner849](https://discourse.threejs.org/u/prisoner849)\
**Post date:** [February 15, 2025, 7:17pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/25 "2025-02-15T19:17:02Z")

</div>

Seems, the answer is in the description of the issue.

You set camera position without the casting it into object’s local space, when you instantiate material:

```javascript
{camPos: {value: new THREE.Vector3().copy(camera.position)}

```

But, when you move the camera with the controls, now it it’s in object’s local space:

```javascript
  camPos.copy(camera.position);
  cube.worldToLocal(camPos);
  shaderMaterial.uniforms.camPos.value.copy(camPos);

```

---

<div class="post-metadata">

**Author:** ![hofk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/hofk/32/44944_2.png) [@hofk](https://discourse.threejs.org/u/hofk)\
**Post date:** [February 15, 2025, 7:18pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/26 "2025-02-15T19:18:32Z")

</div>

> [@PavelBoytchev](#):
>
> Any plans for animation of SDF objects?

The SDFs are in a box. I can therefore move them as a whole. Let’s see what works.

See [SDFs in the scene - raymarching - #29 by hofk](https://discourse.threejs.org/t/sdfs-in-the-scene-raymarching/78355/29)

---

<div class="post-metadata">

**Author:** ![hofk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/hofk/32/44944_2.png) [@hofk](https://discourse.threejs.org/u/hofk)\
**Post date:** [February 15, 2025, 7:21pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/27 "2025-02-15T19:21:00Z")

</div>

> [@prisoner849](#):
>
> Seems, the answer is in the description of the issue.

You’re so fast, I could have waited. When I posted it, I noticed it myself - but thank you!!!

---

<div class="post-metadata">

**Author:** ![hofk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/hofk/32/44944_2.png) [@hofk](https://discourse.threejs.org/u/hofk)\
**Post date:** [February 15, 2025, 8:10pm UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/28 "2025-02-15T20:10:38Z")

</div>

I’ve added primitive combinations.

[07\_SDF-Shader-Raymarching](https://hofk.de/main/threejs/_SDF/07_SDF-Shader-Raymarching.html)

```javascript

 distCol opUnion(distCol dc1, distCol dc2) { 
  distCol dc;
  float d = min(dc1.d, dc2.d);
  vec3 c = d < dc2.d ? dc1.c : dc2.c; 
  dc.d = d;
  dc.c = c;     
  return dc;
}

distCol opSubtraction(distCol dc1, distCol dc2) { 
  distCol dc;
  float d = max(-dc1.d, dc2.d);
  vec3 c = d > dc2.d ? dc1.c : dc2.c; 
  dc.d = d;
  dc.c = c;     
  return dc;
}

```

```javascript
  dc = opSubtraction(dcSph, dcTor);
  dc = opUnion(dc, dcRBox );

```

 ![2025-02-15 21.01.21](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/6/c/6c3bd1ed4f5f31fab6d80c78c37f6e188bf11008.png)

* * *

UPDATE  
SDF and mesh appear fully integrated .

---

<div class="post-metadata">

**Author:** ![hofk](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/hofk/32/44944_2.png) [@hofk](https://discourse.threejs.org/u/hofk)\
**Post date:** [February 17, 2025, 10:28am UTC](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504/29 "2025-02-17T10:28:03Z")

</div>

> [@PavelBoytchev](#):
>
> Any plans for animation of SDF objects?

So far, something like this is already working in the test example.

```javascript

  vec3 pTransSph = p - vec3( 0.5*cos(time), 0.2, 0.3*sin(time)); // translate  
  distCol dcSph;
  dcSph.d = sdSphere( pTransSph , 0.5 );
  dcSph.c = vec3( 0.6, 0.6, 0.1 );
  
  vec3 pTransTor = p - vec3( 0.4, -0.1, -0.3);
  vec3 pRotTor = rotateX(pTransTor, -0.3*time*PI); // radiant ( -angle)
  distCol dcTor;
  dcTor.d = sdTorus(pRotTor, vec2(0.575, 0.14));
  dcTor.c = vec3( 0.5, 0.1, 0.5 );
  
  distCol dcRBox;
  dcRBox.d = sdRoundBox(p, vec3(0.8, 0.1, 0.2), 0.04);
  dcRBox.c = vec3( 0.1, 0.2, 1.0 );

```

* * *

With movement of the box containing the SDF:

[Previous page](https://discourse.threejs.org/t/how-do-you-integrate-signed-distance-fields-sdf-into-a-basic-three-js-scene/77504.md?page=1)
