With a little help from AI, I can now display the curve.
However, I had imagined the result to be different.
I have hidden the alignment of the capsule using quaternions for the time being. It is not working properly yet.
05_SDF_ShaderSolo V1
The Code:
<!DOCTYPE html>
<!-- https://discourse.threejs.org/t/sdfs-in-the-scene-raymarching/78355 -->
<html>
<head>
<title>05_SDF_ShaderSolo V1</title>
<meta charset="utf-8" />
<style>
body{
overflow: hidden;
margin: 0;
}
</style>
</head>
<body></body>
<script type="module">
// @author hofk
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, 0, 30);
const light = new THREE.AmbientLight(0x404040, 4.5); // soft white light
scene.add(light);
const directionalLight = new THREE.DirectionalLight(0xffffff, 2.5);
directionalLight.position.set(5, 15, 15);
scene.add(directionalLight);
const controls = new OrbitControls(camera, renderer.domElement);
const axesHelper = new THREE.AxesHelper(20);
scene.add(axesHelper);
// Vertex Shader
const vShader = `
varying vec3 vPosition;
varying vec2 vUv;
void main() {
vPosition = position;
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}
`;
// Fragment Shader
const fShader = `
uniform float time;
uniform vec3 camPos;
uniform vec2 resolution;
uniform mat4 projectionMatrix;
uniform mat4 modelViewMatrix;
varying vec3 vPosition;
varying vec2 vUv;
#define MAX_STEPS 250
#define MAX_DIST 100.0
#define SURF_DIST 1e-4
#define PI 3.1415926
// distance color
struct distCol {
float d;
vec4 c;
};
// quaternion
struct quat {
float s;
vec3 v;
};
quat mul(float s, quat q) {
return quat(s * q.s, s * q.v);
}
quat conjugate(quat q) {
return quat(q.s, -q.v);
}
float norm_squared(quat q) {
return q.s * q.s + dot(q.v, q.v);
}
quat div(quat q, float s) {
return quat(q.s / s, q.v / s);
}
quat mul(quat a, quat b) {
return quat(a.s * b.s - dot(a.v, b.v), a.s * b.v + b.s * a.v + cross(a.v, b.v));
}
quat invert(quat q) {
return div(conjugate(q), norm_squared(q));
}
quat div(quat a, quat b) {
return mul(a, invert(b));
}
vec3 rotate(quat q, vec3 p) {
return mul(mul(q, quat(0.0, p)), invert(q)).v;
}
quat setFromBasis(vec3 e1, vec3 e2, vec3 e3) {
float s;
float x;
float y;
float z;
float dis;
float nd;
float trace = e1.x + e2.y + e3.z;
if(trace > 0.0) {
dis = trace + 1.0;
dis = dis > 0.0 ? dis : 1e5;
nd = 0.5 / sqrt(dis);
s = 0.25 / nd;
x = -(e3.y - e2.z) * nd;
y = -(e1.z - e3.x) * nd;
z = -(e2.x - e1.y) * nd;
} else {
if(e1.x > e2.y && e1.x > e3.z) {
dis = 1.0 + e1.x - e2.y - e3.z;
dis = dis > 0.0 ? dis : 1e5;
nd = 2.0 * sqrt(dis);
s = (e3.y - e2.z) / nd;
x = -0.25 * nd;
y = -(e1.y + e2.x) / nd;
z = -(e1.z + e3.x) / nd;
} else {
if(e2.y > e3.z) {
dis = 1.0 + e2.y - e1.x - e3.z;
dis = dis > 0.0 ? dis : 1e5;
nd = 2.0 * sqrt(dis);
s = (e1.z - e3.x) / nd;
x = -(e1.y + e2.x) / nd;
y = -0.25 * nd;
z = -(e2.z + e3.y) / nd;
} else {
dis = 1.0 + e3.z - e1.x - e2.y;
dis = dis > 0.0 ? dis : 1e5;
nd = 2.0 * sqrt(dis);
s = (e2.x - e1.y) / nd;
x = -(e1.z + e3.x) / nd;
y = -(e2.z + e3.y) / nd;
z = -0.25 * nd;
}
}
}
return quat(s, vec3(x, y, z));
}
float sdVerticalCapsule(vec3 p, float h, float r) {
p.y -= clamp(p.y, 0.0, h);
return length(p) - r;
}
float dot2( in vec3 v ) { return dot(v,v); }
float udQuad( vec3 p, vec3 a, vec3 b, vec3 c, vec3 d )
{
vec3 ba = b - a; vec3 pa = p - a;
vec3 cb = c - b; vec3 pb = p - b;
vec3 dc = d - c; vec3 pc = p - c;
vec3 ad = a - d; vec3 pd = p - d;
vec3 nor = cross( ba, ad );
return sqrt(
(sign(dot(cross(ba,nor),pa)) +
sign(dot(cross(cb,nor),pb)) +
sign(dot(cross(dc,nor),pc)) +
sign(dot(cross(ad,nor),pd))<3.0)
?
min( min( min(
dot2(ba*clamp(dot(ba,pa)/dot2(ba),0.0,1.0)-pa),
dot2(cb*clamp(dot(cb,pb)/dot2(cb),0.0,1.0)-pb) ),
dot2(dc*clamp(dot(dc,pc)/dot2(dc),0.0,1.0)-pc) ),
dot2(ad*clamp(dot(ad,pd)/dot2(ad),0.0,1.0)-pd) )
:
dot(nor,pa)*dot(nor,pa)/dot2(nor) );
}
distCol opUnion(distCol dc1, distCol dc2) {
distCol dc;
float d = min(dc1.d, dc2.d);
vec4 c = d < dc2.d ? dc1.c : dc2.c;
dc.d = d;
dc.c = c;
return dc;
}
vec3 translateXYZ(vec3 p, vec3 q) {
return p - q;
}
// CatmullRomCurve
// ----------------------
const int NUM_POINTS = 5;
int numPoints = NUM_POINTS;
float eps = 0.001;
vec3 controlPoints[NUM_POINTS] = vec3[](
vec3( 10.0, 0.0, 0.0),
vec3( 15.0, 5.0, 8.0),
vec3( 5.0, 15.0, 0.0),
vec3( -5.0, 10.0, 5.0),
vec3(-10.0, 0.0, 5.0)
);
vec3 catmullRom(vec3 p0, vec3 p1, vec3 p2, vec3 p3, float t) {
float t2 = t * t;
float t3 = t2 * t;
return 0.5 * ((2.0 * p1) +
(-p0 + p2) * t +
(2.0 * p0 - 5.0 * p1 + 4.0 * p2 - p3) * t2 +
(-p0 + 3.0 * p1 - 3.0 * p2 + p3) * t3);
}
vec3 getCurvePoint(int i, float t, int numPoints) {
int i0 = (i - 1 + numPoints) % numPoints;
int i1 = i;
int i2 = (i + 1) % numPoints;
int i3 = (i + 2) % numPoints;
return catmullRom(controlPoints[i0], controlPoints[i1], controlPoints[i2], controlPoints[i3], t);
}
vec3 getMovingCapsulePos() {
float u = mod(0.05 * time, 1.0); // globaler Parameter 0 .. 1
float segFloat = u * float(numPoints);
int seg = int(floor(segFloat));
float tCurve = fract(segFloat);
return getCurvePoint(seg, tCurve, numPoints);
}
vec3 getTangent() {
float u = mod(0.05 * time, 1.0);
float segFloat = u * float(numPoints);
int seg = int(floor(segFloat));
float tCurve = fract(segFloat);
return normalize(getCurvePoint(seg, tCurve + eps, numPoints) - getCurvePoint(seg, tCurve - eps, numPoints));
}
void getFrenetFrame(out vec3 tangent, out vec3 normal, out vec3 binormal) {
tangent = getTangent();
vec3 up = abs(tangent.y) < 0.99 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0);
binormal = normalize(cross(tangent, up));
normal = cross(binormal, tangent);
}
vec2 projectToScreen(vec3 worldPos) {
vec4 clipPos = projectionMatrix * modelViewMatrix * vec4(worldPos, 1.0);
vec3 ndc = clipPos.xyz / clipPos.w;
return ndc.xy * 0.5 + 0.5;
}
float getCurveScreenDist() {
float minDist = 100.0;
const int numSamples = 50;
vec2 fragCoordNormalized = gl_FragCoord.xy / resolution;
for (int i = 0; i < numSamples; i++) {
float uSample = float(i) / float(numSamples - 1); // u 0 .. 1
float segFloat = uSample * float(numPoints);
int seg = int(floor(segFloat));
float tCurve = fract(segFloat);
vec3 posWorld = getCurvePoint(seg, tCurve, numPoints);
vec2 posScreen = projectToScreen(posWorld);
float d = distance(fragCoordNormalized, posScreen);
minDist = min(minDist, d);
}
return minDist;
}
distCol GetDist(vec3 p) {
distCol dc;
//vec3 tangent, normal, binormal;
//getFrenetFrame(tangent, normal, binormal);
//quat qu = setFromBasis(tangent, normal, binormal);
distCol dcVertCapsule;
vec3 posOnCurve = getMovingCapsulePos();
//dcVertCapsule.d = sdVerticalCapsule(rotate(qu, translateXYZ(p, posOnCurve)), 2.0, 0.5);
dcVertCapsule.d = sdVerticalCapsule( translateXYZ(p, posOnCurve), 2.0, 0.5);
dcVertCapsule.c = vec4(0.5, 0.5, 0.5, 1.0);
distCol dcQuad;
dcQuad.d = udQuad(p, vec3(-1000.0, -1000.0, 0.0), vec3(500.0, -500.0, 0.0), vec3(500.0, 500.0, 0.0), vec3(-500.0, 500.0, 0.0));
dcQuad.c= vec4(0.1, 0.2, 9.0, 1.0);
dc = dcQuad;
dc = opUnion( dc,dcVertCapsule);
return dc;
}
distCol RayMarch(vec3 ro, vec3 rd) {
distCol dc;
float dO = 0.0;
for (int i = 0; i < MAX_STEPS; i++) {
vec3 p = ro + rd * dO;
dc = GetDist(p);
dO += dc.d;
if (dO > MAX_DIST || dc.d < SURF_DIST) break;
}
dc.d = dO;
return dc;
}
vec3 GetNormal(vec3 p) {
float d = GetDist(p).d;
vec2 e = vec2(SURF_DIST, 0.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.0;
float sca = 1.0;
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.0, 1.0);
return dif;
}
void main() {
vec2 uv = vUv - 0.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 p = ro + rd * dc.d;
vec3 lightPos = vec3(2.0, 16.0, 3.0);
float diff = GetLight(p, lightPos);
float ao = 0.051 * GetAo(p, GetNormal(p));
vec4 ct = dc.c;
vec3 c = ct.rgb;
vec3 sceneColor = 0.7 * c + 0.5 * diff + 0.2 * ao;
float curveScreenDist = getCurveScreenDist();
float lineThickness = 0.004;
float curveOverlay = smoothstep(lineThickness, 0.0, curveScreenDist);
vec3 overlayColor = vec3(1.0, 1.0, 0.0);
vec3 finalColor = mix(sceneColor, overlayColor, curveOverlay);
gl_FragColor = vec4(finalColor, ct.a);
}
}
`;
const boxParam = [ 1000.0, 1000.0, 1000.0, 0.0, 0.0, 0.0 ];
let boxGeo;
let box;
let shaderMaterial;
let camPos;
boxGeo = new THREE.BoxGeometry(boxParam[0], boxParam[1], boxParam[2]);
shaderMaterial = new THREE.ShaderMaterial({
uniforms: {
camPos: { value: new THREE.Vector3().copy(camera.position) },
time: { value: 0.0 },
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
},
vertexShader: vShader,
fragmentShader: fShader,
side: THREE.DoubleSide,
transparent: false,
});
box = new THREE.Mesh(boxGeo, shaderMaterial);
scene.add(box);
box.position.set(boxParam[3], boxParam[4], boxParam[5]);
camPos = new THREE.Vector3();
controls.addEventListener("change", event => {
camPos.copy(camera.position);
box.worldToLocal(camPos);
shaderMaterial.uniforms.camPos.value.copy(camPos);
}, false);
const clock = new THREE.Clock();
let t = 0.0;
camPos.copy(camera.position);
box.worldToLocal(camPos);
shaderMaterial.uniforms.camPos.value.copy(camPos);
animate();
function animate() {
t = clock.getElapsedTime();
requestAnimationFrame(animate);
shaderMaterial.uniforms.time.value = t;
renderer.render(scene, camera);
}
</script>
</html>