This is an extension of Intro to Raymarching but with added Mouse Control for orbiting around the sphere + shadows.
precision mediump float;
uniform vec2 iResolution;
uniform float iTime;
uniform vec4 iMouse;
// --------------------------------------------------
// SDF scene
// --------------------------------------------------
float sdSphere(vec3 p, float r) {
return length(p) - r;
}
float sdFloor(vec3 p) {
return p.y + 1.0; // floor at y = -1
}
float scene(vec3 p) {
float sphere = sdSphere(p - vec3(0.0, 0.0, 0.0), 0.8);
float floorD = sdFloor(p);
return min(sphere, floorD);
}
// --------------------------------------------------
// Helpers
// --------------------------------------------------
vec3 getNormal(vec3 p) {
vec2 e = vec2(0.001, 0.0);
return normalize(vec3(
scene(p + e.xyy) - scene(p - e.xyy),
scene(p + e.yxy) - scene(p - e.yxy),
scene(p + e.yyx) - scene(p - e.yyx)
));
}
mat3 cameraLookAt(vec3 ro, vec3 ta, float roll) {
vec3 ww = normalize(ta - ro);
vec3 uu = normalize(cross(ww, vec3(sin(roll), cos(roll), 0.0)));
vec3 vv = normalize(cross(uu, ww));
return mat3(uu, vv, ww);
}
// --------------------------------------------------
// Main
// --------------------------------------------------
void main() {
vec2 fragCoord = gl_FragCoord.xy;
vec2 uv = (fragCoord - 0.5 * iResolution.xy) / iResolution.y;
// Normalised mouse in 0..1 range
vec2 m = iMouse.xy / iResolution.xy;
// Fallback slow auto orbit when mouse not pressed and no mouse available
bool mouseActive = (iMouse.z > 0.0) || (iMouse.x > 0.0 || iMouse.y > 0.0);
float yaw;
float pitch;
float dist;
if (mouseActive) {
// Horizontal mouse = yaw
yaw = (m.x - 0.5) * 6.28318;
// Vertical mouse = pitch
pitch = (m.y - 0.5) * 2.4;
pitch = clamp(pitch, -1.2, 1.2);
// Also use vertical mouse to control zoom a bit
dist = mix(2.5, 10.0, 1.0 - m.y);
} else {
yaw = iTime * 0.35;
pitch = 0.25 + 0.2 * sin(iTime * 0.7);
dist = 6.0;
}
vec3 target = vec3(0.0, 0.0, 0.0);
// Orbit camera around target
vec3 ro = target + vec3(
cos(pitch) * sin(yaw),
sin(pitch),
cos(pitch) * cos(yaw)
) * dist;
mat3 cam = cameraLookAt(ro, target, 0.0);
vec3 rd = normalize(cam * vec3(uv, 1.5));
// Raymarch
float t = 0.0;
bool hit = false;
for (int i = 0; i < 200; i++) {
vec3 p = ro + rd * t;
float d = scene(p);
if (d < 0.001) {
hit = true;
break;
}
t += d;
if (t > 50.0) break;
}
vec3 col = vec3(0.1, 0.1, 0.15);
if (hit) {
vec3 p = ro + rd * t;
vec3 n = getNormal(p);
vec3 lightDir = normalize(vec3(1.0, 1.0, 1.0));
float diff = max(dot(n, lightDir), 0.0);
vec3 albedo = vec3(0.8, 0.3, 0.2);
if (p.y < -0.99) {
float check = mod(floor(p.x) + floor(p.z), 2.0);
albedo = mix(vec3(0.3), vec3(0.8), check);
}
// Specular
vec3 viewDir = normalize(ro - p);
vec3 halfDir = normalize(lightDir + viewDir);
float spec = pow(max(dot(n, halfDir), 0.0), 64.0);
// Soft-ish shadow
float shadow = 1.0;
float st = 0.02;
for (int j = 0; j < 40; j++) {
vec3 sp = p + n * 0.01 + lightDir * st;
float h = scene(sp);
shadow = min(shadow, 12.0 * h / st);
if (h < 0.001) {
shadow = 0.0;
break;
}
st += clamp(h, 0.01, 0.2);
if (st > 20.0) break;
}
shadow = clamp(shadow, 0.0, 1.0);
float ambient = 0.2;
col = albedo * (ambient + diff * 0.8 * shadow) + spec * 0.35 * shadow;
}
col = pow(col, vec3(1.0 / 2.2));
gl_FragColor = vec4(col, 1.0);
}