TIL gl_DepthRange. Wow! How long has this been available?
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 ?
I have now generated a variant of the integration of the SDFs into the three.js scene, which I like quite a bit.
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.
UPDATE:
found
camPos.copy(camera.position);
cube.worldToLocal(camPos);
shaderMaterial.uniforms.camPos.value.copy(camPos);
then
renderer.setAnimationLoop( () => { renderer.render(scene, camera);});
The code:
<!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>
Any plans for animation of SDF objects?
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:
{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:
camPos.copy(camera.position);
cube.worldToLocal(camPos);
shaderMaterial.uniforms.camPos.value.copy(camPos);
The SDFs are in a box. I can therefore move them as a whole. Let’s see what works.
You’re so fast, I could have waited. When I posted it, I noticed it myself - but thank you!!!
I’ve added primitive combinations.
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;
}
dc = opSubtraction(dcSph, dcTor);
dc = opUnion(dc, dcRBox );
UPDATE
SDF and mesh appear fully integrated .
So far, something like this is already working in the test example.
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: