const STAR_MAX_COUNT = 16; const BLACKHOLE_MAX_COUNT = 5; const STAR_COLOR = [0.956,0.5,0.215]; var vShader =` uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; uniform mat4 uModelMatrix; uniform mat4 uNormalMatrix; uniform bool uUseNormalMatrix; uniform vec3 uAmbientColor; uniform vec3 uDiffuseColor; uniform vec3 uLightDir; attribute vec3 aXYZ; attribute vec3 aColor; attribute vec3 aNormal; varying vec3 vColor; varying vec3 vXYZ; void main () { mat4 mvMatrix = uViewMatrix * uModelMatrix; gl_Position = uProjectionMatrix * mvMatrix * vec4(aXYZ,1); mat4 nMatrix = uUseNormalMatrix?uNormalMatrix:mvMatrix; vColor = uAmbientColor*aColor; vec3 light = normalize(-uLightDir); vec3 normal = vec3(normalize(nMatrix*vec4(aNormal,0))); vColor += aColor*uDiffuseColor*max(dot(normal,light),0.0); vXYZ = (uModelMatrix * vec4(aXYZ,1)).xyz; } `; var fShader = ` precision highp float; // Needed, since we'll get uniforms which were in vertex shader // Distance a ray should travel inside a black hole, // changing it can alter weirdly the amount of rays that don't escape the black hole #define MAGIC_STEP 0.01 varying vec3 vColor; varying vec3 vXYZ; // This is dumb, but we can't just pass the color from vertex shader, because // we could need it when the fragment shader is ran on the skybox (or just not on a star) // And in those cases, we recalculate the star color, based on where our ray landed uniform vec3 uStarColor; // Settings uniform bool uDisableRayMarching; uniform vec4 uBlack; // color when ray doesn't hit anything (or hits the skybox) uniform vec4 uBHBlack; // color when ray doesn't escape the black hole // For ray marching uniform vec4 uStars[`+STAR_MAX_COUNT+`]; uniform vec4 uBH[`+BLACKHOLE_MAX_COUNT+`]; uniform vec3 uEye; // For light recalculation uniform mat4 uProjectionMatrix; uniform mat4 uViewMatrix; uniform mat4 uModelMatrix; uniform mat4 uNormalMatrix; uniform bool uUseNormalMatrix; uniform vec3 uAmbientColor; uniform vec3 uDiffuseColor; uniform vec3 uLightDir; // Direction implementation of the math from // https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line#Vector_formulation float linePointDistance(vec3 linePoint, vec3 lineDir, vec3 point) { vec3 projectionDir = dot(point - linePoint, lineDir) * lineDir; // aka projectionVector - linePoint float dist = length((point - linePoint) - projectionDir); // aka length of perpendicular vector // if pointing in opposite directions, meaning if the cloest point is behind us if (dot(projectionDir, lineDir) < 0.0) return -dist; return dist; } // Direct implementation of the math from // https://en.wikipedia.org/wiki/Line%E2%80%93sphere_intersection#Calculation_using_vectors_in_3D vec3 spherePoint(vec3 lineOrigin, vec3 lineDir, vec4 sphere) { lineDir = normalize(lineDir); float b = dot(lineDir, lineOrigin - sphere.xyz); float Delta = b * b - (length(lineOrigin - sphere.xyz) * length(lineOrigin - sphere.xyz) - sphere.a * sphere.a); return lineOrigin + min(-b + sqrt(Delta), -b - sqrt(Delta)) * lineDir; } // Direct implementation of the math from // https://en.wikipedia.org/wiki/Dot_product#Geometric_definition float angleBetweenVectors(vec3 v1, vec3 v2) { return acos(radians(dot(v1, v2) / (length(v1) * length(v2)))); } int rayMarchInBH(vec4 BH, inout vec3 ray, inout vec3 step) { ray = spherePoint(ray, step, vec4(BH.xyz, BH.a * 3.0)); // h is used in the step changing calculation float h = length(cross(ray, step)); for (int i = 0; i < 100; i++) { float dist = distance(ray, BH.xyz); // if inside event horizon if (dist <= BH.a) { return 1; } // if outside ISCO if (dist > BH.a * 3.0 + 1.0) { // adding 1 fixes some rays wrongfully passing directly through return 0; } // Gravity bends light (actually spacetime, which in turn changes it's path for outside observers). // This is a more or less correct (as far as I can tell) implementation of Riccardo Antonelli's formula // http://rantonels.github.io/starless/#MathJax-Element-29-Frame // Though I found it difficult to understand, so I referred to some of it's implementations from // https://github.com/rantonels/starless/blob/7a0c3c4efdc0666f53dcfa48d310fb2164be2d1a/tracer.py // and // https://github.com/dbrant/BlackHoleRaytracer/blob/master/Equation/SchwarzschildBlackHoleEquation.cs step += normalize(ray - BH.xyz) * -1.5 * (h*h) * MAGIC_STEP / (dist*dist*dist*dist*dist); ray += step; } return 1; // orbits inside } vec4 calculateColor(bool notBent, vec4 star, vec3 ray, vec3 step) { if (notBent) { return vec4(vColor,1); } // This is an exact copy from the vertex shader light calculations // Since the ray was bent, we don't know what exactly it will hit, so we need to recalculate // This probably isn't entirely correct, but I like how it looks vec3 anormal = spherePoint(ray, step, star) - star.xyz; mat4 mvMatrix = uViewMatrix * uModelMatrix; mat4 nMatrix = uUseNormalMatrix?uNormalMatrix:mvMatrix; vec3 color = uAmbientColor*uStarColor; vec3 light = normalize(-uLightDir); vec3 normal = vec3(normalize(nMatrix*vec4(anormal,0))); color += uStarColor*uDiffuseColor*max(dot(normal,light),0.0); return vec4(color, 1); } // Does the ray, when going in the direction step, eventually hit the surface of the sphere bool hitsSphere(vec3 ray, vec3 step, vec4 sphere) { float dist = linePointDistance(ray, step, sphere.xyz); return dist >= 0.0 && dist < sphere.a; } // I think this slightly differs than the norm, but ray is the current position of my ray // and step is a direction vector, so moving the ray forward is as simple as ray += step vec4 rayMarch(vec3 ray, vec3 step) { // Does the ray pass through any blackholes int bhStatus = 2; for (int i = 0; i < `+BLACKHOLE_MAX_COUNT+`; i++) { if (hitsSphere(ray, step, vec4(uBH[i].xyz, uBH[i].a * 3.0))) { // Try to enter black hole, only if there isn't a star that is in front float bhDist = distance(uBH[i].xyz, ray); for (int j = 0; j < `+STAR_MAX_COUNT+`; j++) { if (distance(uStars[j].xyz, ray) < bhDist && hitsSphere(ray, step, uStars[j])) bhDist = -1.0; } if (bhDist > -1.0) bhStatus = rayMarchInBH(uBH[i], ray, step); } } if (bhStatus == 1) { return uBHBlack; } // Does the ray hit a star (even when it has escaped a black hole) for (int i = 0; i < `+STAR_MAX_COUNT+`; i++) { if (hitsSphere(ray, step, uStars[i])) { return calculateColor(bhStatus == 2, uStars[i], ray, step); } } return uBlack; } void main() { vec3 step = normalize(vXYZ - uEye); gl_FragColor = (uDisableRayMarching) ? vec4(vColor, 1.0) : rayMarch(uEye, step); } `;